Compare commits

...

124 Commits

Author SHA1 Message Date
Frappe PR Bot
32abf67c80 chore(release): Bumped to Version 14.0.3
## [14.0.3](https://github.com/frappe/erpnext/compare/v14.0.2...v14.0.3) (2022-08-30)

### Bug Fixes

* AD not getting copied from SCO while creating a SE (backport [#32004](https://github.com/frappe/erpnext/issues/32004)) ([#32007](https://github.com/frappe/erpnext/issues/32007)) ([cee867f](cee867f941))
* Add docstatus filter for voucher_no in Repost Item Valuation ([309da96](309da96442))
* add validation for PO in Stock Entry (backport [#31974](https://github.com/frappe/erpnext/issues/31974)) ([#31975](https://github.com/frappe/erpnext/issues/31975)) ([4d8ced6](4d8ced6c87))
* default supplier not set in the PP ([4cf9fb0](4cf9fb08e1))
* display amount in account currency if party is supplied ([15915d7](15915d7053))
* Explicitly commit "log_error" since its getting called during GET request (backport [#31952](https://github.com/frappe/erpnext/issues/31952)) ([#31953](https://github.com/frappe/erpnext/issues/31953)) ([362976f](362976fa42))
* gl entries for asset repair ([f0f7afa](f0f7afa669))
* lost quotation not to expired ([8717235](8717235a34))
* material request connection on work order ([193502c](193502ce03))
* **patch:** update sla doctype directly (backport [#32014](https://github.com/frappe/erpnext/issues/32014)) ([#32015](https://github.com/frappe/erpnext/issues/32015)) ([1b5a1cb](1b5a1cbaad))
* permissions for Task Type ([#32016](https://github.com/frappe/erpnext/issues/32016)) ([7ca1beb](7ca1beb15d))
* Purchase Order creation from Sales Order ([39ff0cc](39ff0cc6d8))
* Purposes not set ([94ebfa7](94ebfa765c))
* restrict party types to Supplier/Customer for AR/AP report ([c407d1e](c407d1e51a))
* Rounded total for cash and non trade discount invoices ([6c4fcd8](6c4fcd80c6))
* Route condition set for stock ledger (backport [#31935](https://github.com/frappe/erpnext/issues/31935)) ([#31945](https://github.com/frappe/erpnext/issues/31945)) ([a7d23ab](a7d23abc2f))
2022-08-30 09:24:00 +00:00
Deepesh Garg
52b42e9492 Merge pull request #32025 from frappe/version-14-hotfix
chore: weekly version-14 release
2022-08-30 14:52:11 +05:30
Deepesh Garg
77ac7f06d4 Merge pull request #32021 from frappe/mergify/bp/version-14-hotfix/pr-32005
fix: lost quotation not to expired (backport #32005)
2022-08-30 11:08:40 +05:30
MOHAMMED NIYAS
8717235a34 fix: lost quotation not to expired
(cherry picked from commit 69ffef8c0e)
2022-08-30 05:03:44 +00:00
Deepesh Garg
670426f428 Merge pull request #32017 from frappe/mergify/bp/version-14-hotfix/pr-32016
fix: permissions for Task Type (backport #32016)
2022-08-30 08:38:14 +05:30
mergify[bot]
1b5a1cbaad fix(patch): update sla doctype directly (backport #32014) (#32015)
fix(patch): update sla doctype directly (#32014)

fix: update sla doctype directly
(cherry picked from commit 2d41704424)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-29 22:17:39 +05:30
Raffael Meyer
7ca1beb15d fix: permissions for Task Type (#32016)
(cherry picked from commit 73f4d5931d)
2022-08-29 16:27:00 +00:00
ruthra kumar
c660db145b Merge pull request #32013 from frappe/mergify/bp/version-14-hotfix/pr-32009
refactor: readd remarks field to payment ledger (backport #32009)
2022-08-29 20:21:43 +05:30
ruthra kumar
0aaf9c4f05 chore: add remarks migration to patches.txt
(cherry picked from commit d522f13d55)
2022-08-29 19:52:33 +05:30
ruthra kumar
b3b0272ec9 chore: patch for migrating remarks to payment ledger
(cherry picked from commit 3a6b095ed4)
2022-08-29 14:19:46 +00:00
ruthra kumar
f7898b4954 refactor: re-add remarks field to payment ledger and AR/AP report
(cherry picked from commit 5782c4469a)
2022-08-29 14:19:46 +00:00
Deepesh Garg
b067eae38c Merge pull request #32008 from frappe/mergify/bp/version-14-hotfix/pr-32006
fix: Rounded total for cash and non trade discount invoices (backport #32006)
2022-08-29 17:46:14 +05:30
mergify[bot]
cee867f941 fix: AD not getting copied from SCO while creating a SE (backport #32004) (#32007)
fix: AD not getting copied from SCO while creating a SE (#32004)

(cherry picked from commit 9dbaaa33f5)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-29 15:50:22 +05:30
Deepesh Garg
6c4fcd80c6 fix: Rounded total for cash and non trade discount invoices
(cherry picked from commit 318da16b99)
2022-08-29 10:06:08 +00:00
Deepesh Garg
11657effa5 Merge pull request #31970 from frappe/mergify/bp/version-14-hotfix/pr-31955
chore: update french translation (backport #31955)
2022-08-28 11:32:54 +05:30
Deepesh Garg
d3b7942f32 Merge pull request #31979 from frappe/mergify/bp/version-14-hotfix/pr-31977
fix: restrict party types to Supplier/Customer for AR/AP report (backport #31977)
2022-08-28 11:25:00 +05:30
mergify[bot]
f407c972d1 chore: allow return of components in SCO (backport #31994) (#31995)
chore: allow return of components in SCO (#31994)

chore: allow return of components in sco
(cherry picked from commit af5cbc881f)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-26 23:15:20 +05:30
Deepesh Garg
0b72295fa2 Merge pull request #31990 from frappe/mergify/bp/version-14-hotfix/pr-31988
chore: remove precision on discount_percentage of Sales Invoice Item (backport #31988)
2022-08-26 18:09:26 +05:30
Deepesh Garg
204b6c0272 Merge pull request #31973 from frappe/mergify/bp/version-14-hotfix/pr-31943
fix: gl entries for asset repair (backport #31943)
2022-08-26 17:34:57 +05:30
Deepesh Garg
1118e25b6d Merge pull request #31985 from frappe/mergify/bp/version-14-hotfix/pr-31984
fix: Purchase Order creation from Sales Order (backport #31984)
2022-08-26 16:05:01 +05:30
ruthra kumar
9b9df70632 chore: remove precision on discount_percentage of Sales Invoice Item
(cherry picked from commit c42fef541a)
2022-08-26 10:34:50 +00:00
Solufyin
39ff0cc6d8 fix: Purchase Order creation from Sales Order
(cherry picked from commit bd4b4ddd8b)
2022-08-26 06:31:51 +00:00
ruthra kumar
c407d1e51a fix: restrict party types to Supplier/Customer for AR/AP report
(cherry picked from commit 6aa8fd0f7b)
2022-08-25 10:50:46 +00:00
mergify[bot]
4d8ced6c87 fix: add validation for PO in Stock Entry (backport #31974) (#31975)
fix: add validation for PO in Stock Entry (#31974)

(cherry picked from commit 8566832dd5)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-25 15:29:48 +05:30
Deepesh Garg
4f832678cf chore: fix against account
(cherry picked from commit c1f6dd46d1)
2022-08-25 07:55:08 +00:00
Nabin Hait
f0f7afa669 fix: gl entries for asset repair
(cherry picked from commit b4a2eb2e65)
2022-08-25 07:55:08 +00:00
Florian HENRY
cb6cbf7818 chore: update fr translation
(cherry picked from commit 299da5d596)
2022-08-25 07:37:06 +00:00
Florian HENRY
3aec1175df chore: update fr translation
(cherry picked from commit 1f6f2747d4)
2022-08-25 07:37:06 +00:00
Florian HENRY
c645995ae3 chore: update french translation
(cherry picked from commit 264f98af14)
2022-08-25 07:37:05 +00:00
rohitwaghchaure
f772c17f3f Merge pull request #31969 from frappe/mergify/bp/version-14-hotfix/pr-31967
fix: material request connection on work order (backport #31967)
2022-08-25 12:30:25 +05:30
Rohit Waghchaure
193502ce03 fix: material request connection on work order
(cherry picked from commit 9ab10def49)
2022-08-25 06:59:56 +00:00
rohitwaghchaure
abd637a238 Merge pull request #31968 from frappe/mergify/bp/version-14-hotfix/pr-31966
fix: default supplier not set in the PP (backport #31966)
2022-08-25 12:26:47 +05:30
Rohit Waghchaure
4cf9fb08e1 fix: default supplier not set in the PP
(cherry picked from commit 5fd468d9ec)
2022-08-25 06:55:46 +00:00
Deepesh Garg
e4ca20654f Merge pull request #31964 from frappe/mergify/bp/version-14-hotfix/pr-31909
fix: Add docstatus filter for voucher_no in Repost Item Valuation (backport #31909)
2022-08-25 11:25:02 +05:30
Deepesh Garg
bc873939eb Merge pull request #31959 from frappe/mergify/bp/version-14-hotfix/pr-31934
refactor: disable discount accounting on Buying module(PI) (backport #31934)
2022-08-25 11:06:58 +05:30
Deepesh Garg
c6d3e9f432 Merge pull request #31958 from frappe/mergify/bp/version-14-hotfix/pr-31950
fix: display amount in account currency if party is supplied (backport #31950)
2022-08-25 11:06:42 +05:30
rohitwaghchaure
54f4504df6 Merge pull request #31963 from frappe/mergify/bp/version-14-hotfix/pr-31951
fix: Purposes not set in Maintenance Visit (backport #31951)
2022-08-25 10:50:50 +05:30
Sagar Sharma
309da96442 fix: Add docstatus filter for voucher_no in Repost Item Valuation
(cherry picked from commit 520306dc87)
2022-08-25 05:20:26 +00:00
Rohit Waghchaure
94ebfa765c fix: Purposes not set
(cherry picked from commit f9a7b31b5b)
2022-08-25 05:19:53 +00:00
ruthra kumar
2577747c5c test: remove discount accounting tests
(cherry picked from commit 277ef04b60)
2022-08-25 05:04:46 +00:00
ruthra kumar
fb387426d6 refactor: disable discount accounting on Buying module(PI)
(cherry picked from commit a956e20f29)
2022-08-25 05:04:46 +00:00
ruthra kumar
15915d7053 fix: display amount in account currency if party is supplied
(cherry picked from commit e5b04d54ff)
2022-08-25 05:04:27 +00:00
mergify[bot]
362976fa42 fix: Explicitly commit "log_error" since its getting called during GET request (backport #31952) (#31953)
fix: Explicitly commit "log_error" since its getting called during GET request (#31952)

(cherry picked from commit 122f1c0ced)

Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com>
2022-08-24 18:26:08 +05:30
mergify[bot]
a7d23abc2f fix: Route condition set for stock ledger (backport #31935) (#31945)
fix: Route condition set for stock ledger (#31935)

(cherry picked from commit 0e26df331c)

Co-authored-by: Solufyin <34390782+Solufyin@users.noreply.github.com>
2022-08-24 13:30:25 +05:30
mergify[bot]
4b609322ba chore: add Work Order test dependencies (backport #31936) (#31937)
chore: add Work Order test dependencies (#31936)

(cherry picked from commit fe73d55f70)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2022-08-23 16:09:26 +05:30
Frappe PR Bot
5778f227ee chore(release): Bumped to Version 14.0.2
## [14.0.2](https://github.com/frappe/erpnext/compare/v14.0.1...v14.0.2) (2022-08-23)

### Bug Fixes

* additional-cost in items table ([da69cc5](da69cc5477))
* base_amount and exchange_rate in additional-cost table ([d48487a](d48487ada2))
* Cash and non trade discount calculation ([b6d2de2](b6d2de2cc1))
* don't allow to create SCR directly (backport [#31924](https://github.com/frappe/erpnext/issues/31924)) ([#31926](https://github.com/frappe/erpnext/issues/31926)) ([0bfb774](0bfb774bdf))
* incorrect buying amount in Gross Profit rpt ([981add9](981add9b6f))
* incorrect tax amt due to different exchange rate in PR and PI ([a767326](a76732613e))
* limited options for no-of-employees in crm ([735a608](735a60807a))
* make rate field read-only in subcontracting receipt item (backport [#31905](https://github.com/frappe/erpnext/issues/31905)) ([#31906](https://github.com/frappe/erpnext/issues/31906)) ([6be77d5](6be77d5729))
* map old data as per new options of no-of-employees ([35e9bfc](35e9bfca38))
* **pos:** edge case while closing pos ([#31892](https://github.com/frappe/erpnext/issues/31892)) ([200a971](200a971743))
* process loan interest accrual ([66e5202](66e5202642))
* recalculate rate of items based on "Recalculate Rate" checkbox ([9e60dd3](9e60dd32e8))
* TDS calculation for advance payment ([78b39d6](78b39d6ca4))
* term loan interest calculation ([8fdbbf3](8fdbbf374d))
* test "test_pending_and_received_qty" ([e099e10](e099e10c8e))
* Test cases ([3afb625](3afb625ff8))
* Unable to make payment entry against Fees using education app ([1aa96de](1aa96defda))

### Performance Improvements

* use `create_custom_fields` ([#31853](https://github.com/frappe/erpnext/issues/31853)) ([6656d23](6656d23e45))
2022-08-23 05:28:37 +00:00
Deepesh Garg
7ac75aab1a Merge pull request #31932 from frappe/version-14-hotfix
chore: weekly version 14 release
2022-08-23 10:57:10 +05:30
Deepesh Garg
e831b6e054 Merge pull request #31930 from frappe/mergify/bp/version-14-hotfix/pr-31910
fix: Cash and non trade discount calculation (backport #31910)
2022-08-23 10:36:15 +05:30
Deepesh Garg
856a64b77c chore: Linting issues
(cherry picked from commit 1cb7ae16ab)
2022-08-23 04:49:57 +00:00
Deepesh Garg
3afb625ff8 fix: Test cases
(cherry picked from commit ae3dce0cbd)
2022-08-23 04:49:57 +00:00
Deepesh Garg
b6d2de2cc1 fix: Cash and non trade discount calculation
(cherry picked from commit 3b15966cc9)
2022-08-23 04:49:57 +00:00
mergify[bot]
0bfb774bdf fix: don't allow to create SCR directly (backport #31924) (#31926)
fix: don't allow to create SCR directly (#31924)

(cherry picked from commit bf5c43322a)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-22 18:38:21 +05:30
Deepesh Garg
d7a8db04a1 Merge pull request #31916 from frappe/mergify/bp/version-14-hotfix/pr-31856
fix: incorrect tax amt due to different exchange rate in PR and PI (backport #31856)
2022-08-22 10:57:53 +05:30
Deepesh Garg
2d5ae811d2 Merge pull request #31917 from frappe/mergify/bp/version-14-hotfix/pr-31871
fix: incorrect buying amount in Gross Profit rpt (backport #31871)
2022-08-22 10:56:47 +05:30
Deepesh Garg
be9607e27b Merge pull request #31915 from frappe/mergify/bp/version-14-hotfix/pr-31894
fix: TDS calculation for advance payment (backport #31894)
2022-08-22 10:32:04 +05:30
Deepesh Garg
fd45a7afbe Merge pull request #31818 from frappe/mergify/bp/version-14-hotfix/pr-31816
fix: limited options for no-of-employees in the crm documents (backport #31816)
2022-08-22 10:31:06 +05:30
Deepesh Garg
0c73af6ee2 Merge pull request #31809 from frappe/mergify/bp/version-14-hotfix/pr-31801
Tds report (backport #31801)
2022-08-22 10:30:42 +05:30
ruthra kumar
981add9b6f fix: incorrect buying amount in Gross Profit rpt
(cherry picked from commit 967dd398e7)
2022-08-22 05:00:26 +00:00
Deepesh Garg
8f5736c500 Merge pull request #31832 from frappe/mergify/bp/version-14-hotfix/pr-31830
fix: Unable to make payment entry against Fees using education app (backport #31830)
2022-08-22 10:29:26 +05:30
Deepesh Garg
052f7c3345 Merge pull request #31827 from frappe/mergify/bp/version-14-hotfix/pr-31799
fix: process loan interest accrual (backport #31799)
2022-08-22 10:28:50 +05:30
Deepesh Garg
fa4a40812c Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-31816 2022-08-22 09:09:16 +05:30
ruthra kumar
a76732613e fix: incorrect tax amt due to different exchange rate in PR and PI
(cherry picked from commit 5fd0770372)
2022-08-22 03:38:43 +00:00
Deepesh Garg
dd602989a8 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-31801 2022-08-22 09:00:36 +05:30
Maharshi Patel
78b39d6ca4 fix: TDS calculation for advance payment
"against_voucher": ["is", "not set"] was used in query due to which if TDS was added on "advance" payment vouchers and then reconciled against purchase invoice. it will not find those vouchers and consider this as first-time threshold due to which it will calculate Tax for all transactions.

(cherry picked from commit a452143782)
2022-08-22 03:30:04 +00:00
Deepesh Garg
77fa64e100 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-31799 2022-08-22 08:59:57 +05:30
Deepesh Garg
ac04fc60ef Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-31830 2022-08-22 08:59:31 +05:30
mergify[bot]
6be77d5729 fix: make rate field read-only in subcontracting receipt item (backport #31905) (#31906)
fix: make rate field read-only in subcontracting receipt item (#31905)

(cherry picked from commit 588ca68171)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-20 19:28:21 +05:30
Sagar Sharma
37e5b93e2d Merge pull request #31900 from frappe/mergify/bp/version-14-hotfix/pr-31899
fix: repost item valuation for subcontracting receipt  (backport #31899)
2022-08-19 22:41:28 +05:30
Sagar Sharma
19d29d1861 chore: add option for "Subcontracting Receipt" in "Voucher Type"
(cherry picked from commit f92f3e0208)
2022-08-19 16:31:53 +00:00
Sagar Sharma
0db912998a chore: allow subcontracting receipt backdated entry
(cherry picked from commit f8c11847bb)
2022-08-19 16:31:53 +00:00
mergify[bot]
200a971743 fix(pos): edge case while closing pos (#31892) 2022-08-19 17:11:13 +05:30
Sagar Sharma
c0f0986539 Merge pull request #31897 from frappe/mergify/bp/version-14-hotfix/pr-31890
fix: additional-costs in SCO and SCR (backport #31890)
2022-08-19 15:58:06 +05:30
Sagar Sharma
376293326b chore: conflicts 2022-08-19 15:30:50 +05:30
Sagar Sharma
9a29e3c9f2 chore: add test for additional-cost
(cherry picked from commit c247cf728c)
2022-08-19 09:56:52 +00:00
Sagar Sharma
e099e10c8e fix: test "test_pending_and_received_qty"
(cherry picked from commit addd7347d8)
2022-08-19 09:56:52 +00:00
Sagar Sharma
3b222339b8 chore: add additional-cost table in SCR
(cherry picked from commit 256b4245d5)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
2022-08-19 09:56:51 +00:00
Sagar Sharma
9e60dd32e8 fix: recalculate rate of items based on "Recalculate Rate" checkbox
(cherry picked from commit 2fc6833684)
2022-08-19 09:56:51 +00:00
Sagar Sharma
7ff5414571 chore: move "set_missing_values_in_additional_costs" from SCO to SC"
(cherry picked from commit ea82fe5bc2)
2022-08-19 09:56:50 +00:00
Sagar Sharma
d48487ada2 fix: base_amount and exchange_rate in additional-cost table
(cherry picked from commit eabd3135f0)
2022-08-19 09:56:50 +00:00
Sagar Sharma
da69cc5477 fix: additional-cost in items table
(cherry picked from commit d7ed4093d8)
2022-08-19 09:56:50 +00:00
Sagar Vora
b2a720d847 Merge pull request #31891 from frappe/mergify/bp/version-14-hotfix/pr-31853
perf: use `create_custom_fields` (backport #31853)
2022-08-18 15:57:59 +00:00
Sagar Vora
6656d23e45 perf: use create_custom_fields (#31853)
* perf: use `create_custom_fields`

* fix: default must be a string

(cherry picked from commit aafb735283)
2022-08-18 15:31:42 +00:00
mergify[bot]
ae5c05081d chore: remove unwanted field "provisional_expense_account" from SCR (backport #31847) (#31886)
chore: remove unwanted field "provisional_expense_account" from SCR (#31847)

(cherry picked from commit 7e88eb549f)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-18 17:41:01 +05:30
Frappe PR Bot
028e939cca chore(release): Bumped to Version 14.0.1
## [14.0.1](https://github.com/frappe/erpnext/compare/v14.0.0...v14.0.1) (2022-08-18)

### Bug Fixes

* add asset repair to accounting dimension list ([65bb1d8](65bb1d8cc2))
* Add dimension section in subcontracting doctypes (backport [#31849](https://github.com/frappe/erpnext/issues/31849)) ([#31877](https://github.com/frappe/erpnext/issues/31877)) ([50ad612](50ad612453))
* check item_code in all rows of po_items (backport [#31741](https://github.com/frappe/erpnext/issues/31741)) ([#31842](https://github.com/frappe/erpnext/issues/31842)) ([82f1dd2](82f1dd268d))
* contact search in request for quotation (backport [#31828](https://github.com/frappe/erpnext/issues/31828)) ([#31840](https://github.com/frappe/erpnext/issues/31840)) ([74664a3](74664a34c0))
* delete custom fields on deletion of inventory dimension ([354a9d6](354a9d6169))
* **ecommerce:** remove query to non-existing field (backport [#31771](https://github.com/frappe/erpnext/issues/31771)) ([#31773](https://github.com/frappe/erpnext/issues/31773)) ([8737c10](8737c10ce4))
* getting error to show sales invoice group or print rep… (backport [#31756](https://github.com/frappe/erpnext/issues/31756)) ([#31767](https://github.com/frappe/erpnext/issues/31767)) ([53b9d61](53b9d61c46))
* incorrect produced-qty in production-plan-item (backport [#31706](https://github.com/frappe/erpnext/issues/31706)) ([#31861](https://github.com/frappe/erpnext/issues/31861)) ([010a0ca](010a0ca0a9))
* incorrect rate in BOM exploded items (backport [#31513](https://github.com/frappe/erpnext/issues/31513)) ([#31864](https://github.com/frappe/erpnext/issues/31864)) ([abe1894](abe18945a6))
* intercompany SO throws exception ([1d1f12f](1d1f12f949))
* linter (backport [#31763](https://github.com/frappe/erpnext/issues/31763)) ([#31766](https://github.com/frappe/erpnext/issues/31766)) ([3092131](3092131913))
* Make expense account editable in Purchase Receipt Item (backport [#31730](https://github.com/frappe/erpnext/issues/31730)) ([#31878](https://github.com/frappe/erpnext/issues/31878)) ([b637d4d](b637d4d5f1))
* Make expense account editable in Subcontracting Receipt Item (backport [#31848](https://github.com/frappe/erpnext/issues/31848)) ([#31870](https://github.com/frappe/erpnext/issues/31870)) ([aa5aaa1](aa5aaa113e))
* not able to issue expired batches ([ce5fc5b](ce5fc5b457))
* posting_date of linked vouchers should not affect outstanding ([0faa7b0](0faa7b0432))
* **projects:** Add missing comma ([623f56a](623f56a95c))
* set `billing_address` for purchases in `get_party_details` ([5a28ba8](5a28ba8537))
* set `company_address` for purchases in `party.js` ([4c82533](4c82533239))
* specify allowed doctype in queries (backport [#31761](https://github.com/frappe/erpnext/issues/31761)) ([#31764](https://github.com/frappe/erpnext/issues/31764)) ([6dce122](6dce122825))
* Transit filter for Default Target Warehouse in SE (backport [#31839](https://github.com/frappe/erpnext/issues/31839)) ([#31873](https://github.com/frappe/erpnext/issues/31873)) ([04d3571](04d3571dd9))
2022-08-18 08:30:58 +00:00
Deepesh Garg
216cb9b07b Merge pull request #31881 from frappe/version-14-hotfix
chore: weekly version-14 release
2022-08-18 13:59:28 +05:30
mergify[bot]
50ad612453 fix: Add dimension section in subcontracting doctypes (backport #31849) (#31877)
* fix: Add dimension section in subcontracting doctypes (#31849)

(cherry picked from commit 8704ca783d)

# Conflicts:
#	erpnext/patches.txt

* chore: conflicts

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-18 12:04:07 +05:30
Deepesh Garg
6b71af9008 Merge pull request #31883 from frappe/mergify/bp/version-14-hotfix/pr-31875
fix(projects): Add missing comma (backport #31875)
2022-08-18 11:20:55 +05:30
Deepesh Garg
3c8412efdb Merge pull request #31796 from frappe/mergify/bp/version-14-hotfix/pr-31737
fix: incorrect invoice outstanding in reconciliation tool (backport #31737)
2022-08-18 11:20:26 +05:30
Aditya Hase
623f56a95c fix(projects): Add missing comma
Added with https://github.com/frappe/erpnext/pull/31360

(cherry picked from commit d38778e400)
2022-08-18 05:49:58 +00:00
mergify[bot]
b637d4d5f1 fix: Make expense account editable in Purchase Receipt Item (backport #31730) (#31878)
fix: Make expense account editable in Purchase Receipt Item (#31730)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
(cherry picked from commit 1a6508972e)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2022-08-18 11:16:15 +05:30
rohitwaghchaure
601bc64618 Merge pull request #31869 from frappe/mergify/bp/version-14-hotfix/pr-31860
fix: delete custom fields on deletion of inventory dimension (backport #31860)
2022-08-17 21:45:57 +05:30
mergify[bot]
04d3571dd9 fix: Transit filter for Default Target Warehouse in SE (backport #31839) (#31873)
fix: Transit filter for Default Target Warehouse in SE (#31839)

(cherry picked from commit f1a612245c)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-17 17:26:15 +05:30
mergify[bot]
aa5aaa113e fix: Make expense account editable in Subcontracting Receipt Item (backport #31848) (#31870)
fix: Make expense account editable in Subcontracting Receipt Item (#31848)

(cherry picked from commit 2d04e71412)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-17 16:31:18 +05:30
Rohit Waghchaure
354a9d6169 fix: delete custom fields on deletion of inventory dimension
(cherry picked from commit 0b39a0123e)
2022-08-17 10:14:14 +00:00
rohitwaghchaure
c7e2217c92 Merge pull request #31867 from frappe/mergify/bp/version-14-hotfix/pr-31863
fix: not able to issue expired batches (backport #31863)
2022-08-17 15:43:32 +05:30
Rohit Waghchaure
ce5fc5b457 fix: not able to issue expired batches
(cherry picked from commit 795c94384a)
2022-08-17 09:16:57 +00:00
mergify[bot]
abe18945a6 fix: incorrect rate in BOM exploded items (backport #31513) (#31864)
fix: incorrect rate in BOM exploded items (#31513)

(cherry picked from commit 313625c349)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-17 14:37:40 +05:30
mergify[bot]
010a0ca0a9 fix: incorrect produced-qty in production-plan-item (backport #31706) (#31861)
fix: incorrect produced-qty in production-plan-item (#31706)

(cherry picked from commit 538cd6fdcf)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-17 13:46:33 +05:30
mergify[bot]
74664a34c0 fix: contact search in request for quotation (backport #31828) (#31840)
fix: contact search in request for quotation (#31828)

(cherry picked from commit e5e88bb9f1)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-15 12:19:59 +05:30
mergify[bot]
82f1dd268d fix: check item_code in all rows of po_items (backport #31741) (#31842)
fix: check item_code in all rows of po_items (#31741)

fix: check the item code in each row of PO items
(cherry picked from commit 0047e18a9b)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-08-14 16:00:54 +05:30
Deepesh Garg
1aa96defda fix: Unable to make payment entry against Fees using education app
(cherry picked from commit 79ac50d0f7)
2022-08-11 15:17:42 +00:00
Abhinav Raut
8fdbbf374d fix: term loan interest calculation
(cherry picked from commit 534d7ce64b)
2022-08-11 10:18:24 +00:00
Abhinav Raut
66e5202642 fix: process loan interest accrual
(cherry picked from commit 9ef8d5c5c3)
2022-08-11 10:18:24 +00:00
Nabin Hait
35e9bfca38 fix: map old data as per new options of no-of-employees
(cherry picked from commit 909945c0ac)
2022-08-09 15:18:25 +00:00
Nabin Hait
735a60807a fix: limited options for no-of-employees in crm
(cherry picked from commit 7ecd67605f)
2022-08-09 15:18:25 +00:00
mergify[bot]
c3fd802351 refactor: use browser native lazy loading (backport #31814) (#31815)
refactor: use browser native lazy loading (#31814)

(cherry picked from commit 08d7c48dc7)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-09 18:51:27 +05:30
Deepesh Garg
6046f8bc5e Merge pull request #31798 from frappe/mergify/bp/version-14-hotfix/pr-31779
Bug add accouting dimension in asset repair (backport #31779)
2022-08-09 17:58:35 +05:30
Akash Krishna
d7db8ed12e Tds report (#31801)
* fix: TDS Computation Summary Report not loading, too many values to unpack

(cherry picked from commit 32b30bc5de)
2022-08-09 12:12:33 +00:00
Sagar Vora
adcd21724b Merge pull request #31793 from frappe/mergify/bp/version-14-hotfix/pr-31733
fix: set `billing_address` for purchases in `get_party_details` (backport #31733)
2022-08-08 12:09:43 +00:00
ruthra kumar
04bdff736b chore: patch for creating existing dimensions in asset repair
(cherry picked from commit 80f508c4b1)
2022-08-08 11:10:25 +00:00
ruthra kumar
65bb1d8cc2 fix: add asset repair to accounting dimension list
(cherry picked from commit 452584c4bd)
2022-08-08 11:10:25 +00:00
Deepesh Garg
ebf766cf62 Merge pull request #31794 from frappe/mergify/bp/version-14-hotfix/pr-31777
fix: intercompany SO created from Purchase Order throws exception (backport #31777)
2022-08-08 16:36:46 +05:30
ruthra kumar
80bf47170f test: posting_date should not affect outstanding amount calculation
(cherry picked from commit ef312b8fc4)
2022-08-08 10:59:05 +00:00
ruthra kumar
0faa7b0432 fix: posting_date of linked vouchers should not affect outstanding
posting_date filter should not be applied for linked vouchers.

(cherry picked from commit 5f1562c5b2)
2022-08-08 10:59:05 +00:00
ruthra kumar
1d1f12f949 fix: intercompany SO throws exception
(cherry picked from commit af0a353b79)
2022-08-08 10:36:55 +00:00
Sagar Vora
4c82533239 fix: set company_address for purchases in party.js
(cherry picked from commit d05082987f)
2022-08-08 07:31:44 +00:00
Sagar Vora
5a28ba8537 fix: set billing_address for purchases in get_party_details
(cherry picked from commit a3625b3817)
2022-08-08 07:31:44 +00:00
mergify[bot]
8737c10ce4 fix(ecommerce): remove query to non-existing field (backport #31771) (#31773)
fix(ecommerce): remove query to non-existing field (#31771)

(cherry picked from commit 17b9bfd249)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-03 16:49:16 +05:30
Ankush Menat
2defb89962 ci: fix automated release regex (#31770)
[skip ci]
2022-08-03 16:15:32 +05:30
mergify[bot]
53b9d61c46 fix: getting error to show sales invoice group or print rep… (backport #31756) (#31767)
fix: getting error to show sales invoice group or print rep… (#31756)

fix: formatter getting error to show sales invoice group or print report.

1 - When I view the Gross Profit report in Sales Invoice mode, the table is all broken.
Error on browser console:
TypeError: Cannot read properties of undefined (reading 'indent')

2 - When I try to print, no matter the Group (Sales Invoice, Item Code, Item Group...) nothing happens. in browser log console I have the following error:
TypeError: Cannot read properties of undefined (reading 'content')

i fixed both errors and all working perfectly.

(cherry picked from commit ea88451875)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2022-08-03 11:22:54 +05:30
mergify[bot]
3092131913 fix: linter (backport #31763) (#31766)
fix: linter (#31763)

(cherry picked from commit 9c580dde39)

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2022-08-03 11:17:44 +05:30
mergify[bot]
6dce122825 fix: specify allowed doctype in queries (backport #31761) (#31764)
fix: specify allowed doctype in queries (#31761)

(cherry picked from commit 9baa222976)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-08-03 11:16:41 +05:30
Ankush Menat
248cc48842 ci: setup releases for v14 (#31759)
[skip ci]
2022-08-02 12:11:36 +05:30
Deepesh Garg
ebd8f2f45b chore: verison bump and change log 2022-08-01 22:29:34 +05:30
103 changed files with 1691 additions and 819 deletions

View File

@@ -2,7 +2,7 @@ name: Generate Semantic Release
on:
push:
branches:
- version-13
- version-14
jobs:
release:
name: Release
@@ -13,10 +13,12 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
node-version: 16
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@@ -28,4 +30,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release
run: npx semantic-release

View File

@@ -1,5 +1,5 @@
{
"branches": ["version-13"],
"branches": ["version-14"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
@@ -10,7 +10,7 @@
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
}
],
[
@@ -21,4 +21,4 @@
],
"@semantic-release/github"
]
}
}

View File

@@ -2,7 +2,7 @@ import inspect
import frappe
__version__ = "14.0.0-dev"
__version__ = "14.0.3"
def get_default_company(user=None):

View File

@@ -366,7 +366,7 @@ def update_outstanding_amt(
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
# Didn't use db_set for optimisation purpose
# Didn't use db_set for optimization purpose
ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)

View File

@@ -181,7 +181,11 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Party is mandatory"))
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
if frappe.db.has_column(self.party_type, _party_name):
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
else:
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if not self.party_balance:
@@ -295,6 +299,9 @@ class PaymentEntry(AccountsController):
def validate_reference_documents(self):
valid_reference_doctypes = self.get_valid_reference_doctypes()
if not valid_reference_doctypes:
return
for d in self.get("references"):
if not d.allocated_amount:
continue
@@ -362,7 +369,7 @@ class PaymentEntry(AccountsController):
if not d.allocated_amount:
continue
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount, is_return = frappe.get_cached_value(
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
)
@@ -1184,6 +1191,7 @@ def get_outstanding_reference_documents(args):
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
posting_and_due_date = []
# confirm that Supplier is not blocked
if args.get("party_type") == "Supplier":
@@ -1200,7 +1208,7 @@ def get_outstanding_reference_documents(args):
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
# Get positive outstanding sales /purchase invoices/ Fees
# Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format(
@@ -1224,7 +1232,7 @@ def get_outstanding_reference_documents(args):
condition += " and {0} between '{1}' and '{2}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
@@ -1235,6 +1243,7 @@ def get_outstanding_reference_documents(args):
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
)
@@ -1595,10 +1604,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
elif reference_doctype != "Journal Entry":
if not total_amount:
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
# for handling cases that don't have multi-currency (base field)
total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
total_amount = ref_doc.get("grand_total")
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc.
@@ -1609,7 +1619,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
else:
# Get the exchange rate based on the posting date of the ref doc.
@@ -1627,16 +1637,23 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
def get_payment_entry(
dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
party_type = set_party_type(dt)
if not party_type:
party_type = set_party_type(dt)
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
payment_type = set_payment_type(dt, doc)
if not payment_type:
payment_type = set_payment_type(dt, doc)
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
party_amount, dt, party_account_currency, doc
)
@@ -1786,8 +1803,6 @@ def set_party_account(dt, dn, doc, party_type):
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
elif dt == "Fees":
party_account = doc.receivable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@@ -1803,8 +1818,7 @@ def set_party_account_currency(dt, party_account, doc):
def set_payment_type(dt, doc):
if (
dt == "Sales Order"
or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@@ -1822,18 +1836,15 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
else:
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
outstanding_amount = grand_total - flt(doc.advance_paid)
grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
return grand_total, outstanding_amount

View File

@@ -22,7 +22,8 @@
"amount",
"account_currency",
"amount_in_account_currency",
"delinked"
"delinked",
"remarks"
],
"fields": [
{
@@ -136,12 +137,17 @@
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "remarks",
"fieldtype": "Text",
"label": "Remarks"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-07-11 09:13:54.379168",
"modified": "2022-08-22 15:32:56.629430",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",

View File

@@ -22,6 +22,7 @@ class PaymentReconciliation(Document):
def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = []
self.ple_posting_date_filter = []
@frappe.whitelist()
def get_unreconciled_entries(self):
@@ -150,6 +151,7 @@ class PaymentReconciliation(Document):
return_outstanding = ple_query.get_voucher_outstandings(
vouchers=return_invoices,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True,
@@ -187,6 +189,7 @@ class PaymentReconciliation(Document):
self.party,
self.receivable_payable_account,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
)
@@ -350,6 +353,7 @@ class PaymentReconciliation(Document):
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
self.ple_posting_date_filter.clear()
ple = qb.DocType("Payment Ledger Entry")
self.common_filter_conditions.append(ple.company == self.company)
@@ -359,15 +363,15 @@ class PaymentReconciliation(Document):
if get_invoices:
if self.from_invoice_date:
self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date))
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
if self.to_invoice_date:
self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date))
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
elif get_return_invoices:
if self.from_payment_date:
self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date))
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
if self.to_payment_date:
self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date))
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
def get_conditions(self, get_payments=False):
condition = " and company = '{0}' ".format(self.company)

View File

@@ -283,6 +283,41 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 2)
def test_filter_posting_date_case2(self):
"""
Posting date should not affect outstanding amount calculation
"""
from_date = add_days(nowdate(), -30)
to_date = nowdate()
self.create_payment_entry(amount=25, posting_date=from_date).submit()
self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
pr = self.create_payment_reconciliation()
pr.from_invoice_date = pr.from_payment_date = from_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
pr.from_invoice_date = pr.from_payment_date = to_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
def test_filter_invoice_limit(self):
# check filter condition - invoice limit
transaction_date = nowdate()

View File

@@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', {
});
set_html_data(frm);
if (frm.doc.docstatus == 1) {
if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.nowdate());
}
if (!frm.doc.posting_time) {
frm.set_value("posting_time", frappe.datetime.now_time());
}
}
},
refresh: function(frm) {

View File

@@ -11,6 +11,7 @@
"period_end_date",
"column_break_3",
"posting_date",
"posting_time",
"pos_opening_entry",
"status",
"section_break_5",
@@ -51,7 +52,6 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Period End Date",
"read_only": 1,
"reqd": 1
},
{
@@ -219,6 +219,13 @@
"fieldtype": "Small Text",
"label": "Error",
"read_only": 1
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
}
],
"is_submittable": 1,
@@ -228,10 +235,11 @@
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2021-10-20 16:19:25.340565",
"modified": "2022-08-01 11:37:14.991228",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -278,5 +286,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -15,6 +15,9 @@ from erpnext.controllers.status_updater import StatusUpdater
class POSClosingEntry(StatusUpdater):
def validate(self):
self.posting_date = self.posting_date or frappe.utils.nowdate()
self.posting_time = self.posting_time or frappe.utils.nowtime()
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"posting_date",
"posting_time",
"merge_invoices_based_on",
"column_break_3",
"pos_closing_entry",
@@ -105,12 +106,19 @@
"label": "Customer Group",
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"options": "Customer Group"
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-09-14 11:17:19.001142",
"modified": "2022-08-01 11:36:42.456429",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",
@@ -173,5 +181,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -9,7 +9,7 @@ from frappe import _
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
from frappe.utils import cint, flt, getdate, nowdate
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
from frappe.utils.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive
@@ -99,6 +99,7 @@ class POSInvoiceMergeLog(Document):
sales_invoice.is_consolidated = 1
sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.posting_time = get_time(self.posting_time)
sales_invoice.save()
sales_invoice.submit()
@@ -115,6 +116,7 @@ class POSInvoiceMergeLog(Document):
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
@@ -402,6 +404,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None

View File

@@ -575,7 +575,6 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
@@ -807,7 +806,7 @@ class PurchaseInvoice(BuyingController):
)
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
dummy, amount = self.get_amount_and_base_amount(item, None)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@@ -1165,7 +1164,7 @@ class PurchaseInvoice(BuyingController):
)
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
amount, base_amount = self.get_tax_amounts(tax, None)
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head)
@@ -1791,4 +1790,6 @@ def make_purchase_receipt(source_name, target_doc=None):
target_doc,
)
doc.set_onload("ignore_price_list", True)
return doc

View File

@@ -338,59 +338,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
def test_purchase_invoice_with_discount_accounting_enabled(self):
discount_account = create_account(
account_name="Discount Account",
parent_account="Indirect Expenses - _TC",
company="_Test Company",
)
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 225.0, nowdate()],
["Discount Account - _TC", 0.0, 25.0, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
additional_discount_account = create_account(
account_name="Discount Account",
parent_account="Indirect Expenses - _TC",
company="_Test Company",
)
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
pi.apply_discount_on = "Grand Total"
pi.additional_discount_account = additional_discount_account
pi.additional_discount_percentage = 10
pi.disable_rounded_total = 1
pi.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10,
},
)
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 247.5, nowdate()],
["Discount Account - _TC", 0.0, 27.5, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()

View File

@@ -479,9 +479,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
is_cash_or_non_trade_discount() {
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount);
if (!this.frm.doc.is_cash_or_non_trade_discount) {
this.frm.set_value("additional_discount_account", "");
}
this.calculate_taxes_and_totals();
}
};

View File

@@ -1033,22 +1033,6 @@ class SalesInvoice(SellingController):
)
)
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
gl_entries.append(
self.get_gl_dict(
{
"account": self.additional_discount_account,
"against": self.debit_to,
"debit": self.base_discount_amount,
"debit_in_account_currency": self.discount_amount,
"cost_center": self.cost_center,
"project": self.project,
},
self.currency,
item=self,
)
)
def make_tax_gl_entries(self, gl_entries):
enable_discount_accounting = cint(
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
@@ -2103,13 +2087,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
source_document_warehouse_field = "target_warehouse"
target_document_warehouse_field = "from_warehouse"
received_items = get_received_items(source_name, target_doctype, target_detail_field)
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = "from_warehouse"
target_document_warehouse_field = "target_warehouse"
received_items = get_received_items(source_name, target_doctype, target_detail_field)
received_items = {}
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)

View File

@@ -282,7 +282,6 @@
"label": "Discount (%) on Price List Rate with Margin",
"oldfieldname": "adj_rate",
"oldfieldtype": "Float",
"precision": "2",
"print_hide": 1
},
{
@@ -846,7 +845,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-06-17 05:33:15.335912",
"modified": "2022-08-26 12:06:31.205417",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -318,7 +318,6 @@ def get_advance_vouchers(
"is_cancelled": 0,
"party_type": party_type,
"party": ["in", parties],
"against_voucher": ["is", "not set"],
}
if company:

View File

@@ -207,7 +207,7 @@ def set_address_details(
)
if company_address:
party_details.update({"company_address": company_address})
party_details.company_address = company_address
else:
party_details.update(get_company_address(company))
@@ -219,12 +219,31 @@ def set_address_details(
get_regional_address_details(party_details, doctype, company)
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
if party_details.company_address:
party_details["shipping_address"] = shipping_address or party_details["company_address"]
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
if shipping_address:
party_details.update(
get_fetch_values(doctype, "shipping_address", party_details.shipping_address)
shipping_address=shipping_address,
shipping_address_display=get_address_display(shipping_address),
**get_fetch_values(doctype, "shipping_address", shipping_address)
)
if party_details.company_address:
# billing address
party_details.update(
billing_address=party_details.company_address,
billing_address_display=(
party_details.company_address_display or get_address_display(party_details.company_address)
),
**get_fetch_values(doctype, "billing_address", party_details.company_address)
)
# shipping address - if not already set
if not party_details.shipping_address:
party_details.update(
shipping_address=party_details.billing_address,
shipping_address_display=party_details.billing_address_display,
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
)
get_regional_address_details(party_details, doctype, company)
return party_details.get(billing_address_field), party_details.shipping_address_name

View File

@@ -178,6 +178,11 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Data",
"hidden": 1
},
{
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check",
},
{
"fieldname": "customer_name",
"label": __("Customer Name"),

View File

@@ -119,6 +119,7 @@ class ReceivablePayableReport(object):
party_account=ple.account,
posting_date=ple.posting_date,
account_currency=ple.account_currency,
remarks=ple.remarks,
invoiced=0.0,
paid=0.0,
credit_note=0.0,
@@ -165,7 +166,7 @@ class ReceivablePayableReport(object):
"range4",
"range5",
"future_amount",
"remaining_balance"
"remaining_balance",
]
def get_voucher_balance(self, ple):
@@ -187,7 +188,11 @@ class ReceivablePayableReport(object):
if not row:
return
amount = ple.amount
# amount in "Party Currency", if its supplied. If not, amount in company currency
if self.filters.get(scrub(self.party_type)):
amount = ple.amount_in_account_currency
else:
amount = ple.amount
amount_in_account_currency = ple.amount_in_account_currency
# update voucher
@@ -685,9 +690,10 @@ class ReceivablePayableReport(object):
ple.party,
ple.posting_date,
ple.due_date,
ple.account_currency.as_("currency"),
ple.account_currency,
ple.amount,
ple.amount_in_account_currency,
ple.remarks,
)
.where(ple.delinked == 0)
.where(Criterion.all(self.qb_selection_filter))
@@ -722,6 +728,7 @@ class ReceivablePayableReport(object):
def prepare_conditions(self):
self.qb_selection_filter = []
party_type_field = scrub(self.party_type)
self.qb_selection_filter.append(self.ple.party_type == self.party_type)
self.add_common_filters(party_type_field=party_type_field)
@@ -965,6 +972,9 @@ class ReceivablePayableReport(object):
options="Supplier Group",
)
if self.filters.show_remarks:
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
if not fieldname:
fieldname = scrub(label)

View File

@@ -44,14 +44,14 @@ frappe.query_reports["Gross Profit"] = {
"parent_field": "parent_invoice",
"initial_depth": 3,
"formatter": function(value, row, column, data, default_formatter) {
if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
column._options = "Sales Invoice";
} else {
column._options = "Item";
}
value = default_formatter(value, row, column, data);
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html();

View File

@@ -616,7 +616,7 @@ class GrossProfitGenerator(object):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
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)
else:

View File

@@ -14,9 +14,9 @@ def execute(filters=None):
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
final_result = group_by_supplier_and_category(res)
return columns, final_result

View File

@@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
print(journal_entry_party_map)
out = []
for name, details in gle_map.items():

View File

@@ -823,7 +823,13 @@ def get_held_invoices(party_type, party):
def get_outstanding_invoices(
party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None
party_type,
party,
account,
common_filter=None,
posting_date=None,
min_outstanding=None,
max_outstanding=None,
):
ple = qb.DocType("Payment Ledger Entry")
@@ -850,6 +856,7 @@ def get_outstanding_invoices(
ple_query = QueryPaymentLedger()
invoice_list = ple_query.get_voucher_outstandings(
common_filter=common_filter,
posting_date=posting_date,
min_outstanding=min_outstanding,
max_outstanding=max_outstanding,
get_invoices=True,
@@ -1417,6 +1424,7 @@ def create_payment_ledger_entry(
"amount": dr_or_cr,
"amount_in_account_currency": dr_or_cr_account_currency,
"delinked": True if cancel else False,
"remarks": gle.remarks,
}
)
@@ -1501,6 +1509,7 @@ class QueryPaymentLedger(object):
# query filters
self.vouchers = []
self.common_filter = []
self.voucher_posting_date = []
self.min_outstanding = None
self.max_outstanding = None
@@ -1571,6 +1580,7 @@ class QueryPaymentLedger(object):
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_voucher_no))
.where(Criterion.all(self.common_filter))
.where(Criterion.all(self.voucher_posting_date))
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
)
@@ -1652,6 +1662,7 @@ class QueryPaymentLedger(object):
self,
vouchers=None,
common_filter=None,
posting_date=None,
min_outstanding=None,
max_outstanding=None,
get_payments=False,
@@ -1671,6 +1682,7 @@ class QueryPaymentLedger(object):
self.reset()
self.vouchers = vouchers
self.common_filter = common_filter or []
self.voucher_posting_date = posting_date or []
self.min_outstanding = min_outstanding
self.max_outstanding = max_outstanding
self.get_payments = get_payments

View File

@@ -1454,12 +1454,14 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
return item
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
company.depreciation_expense_account = "_Test Depreciations - _TC"
company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
company.depreciation_cost_center = "_Test Cost Center - _TC"
def set_depreciation_settings_in_company(company=None):
if not company:
company = "_Test Company"
company = frappe.get_doc("Company", company)
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr
company.depreciation_expense_account = "_Test Depreciations - " + company.abbr
company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr
company.depreciation_cost_center = "Main - " + company.abbr
company.save()
# Enable booking asset depreciation entry automatically

View File

@@ -76,7 +76,7 @@ frappe.ui.form.on('Asset Repair Consumed Item', {
'warehouse': frm.doc.warehouse,
'qty': item.consumed_quantity,
'serial_no': item.serial_no,
'company': frm.doc.company
'company': frm.doc.company,
};
frappe.call({

View File

@@ -238,7 +238,6 @@
"no_copy": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "purchase_invoice",
"fieldtype": "Link",
"label": "Purchase Invoice",
@@ -257,6 +256,7 @@
"fieldname": "stock_entry",
"fieldtype": "Link",
"label": "Stock Entry",
"no_copy": 1,
"options": "Stock Entry",
"read_only": 1
}
@@ -264,10 +264,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-06-25 13:14:38.307723",
"modified": "2022-08-16 15:55:25.023471",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -303,6 +304,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",
"track_changes": 1,
"track_seen": 1

View File

@@ -1,11 +1,11 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.assets.doctype.asset.asset import get_asset_account
from erpnext.controllers.accounts_controller import AccountsController
@@ -17,7 +17,7 @@ class AssetRepair(AccountsController):
self.update_status()
if self.get("stock_items"):
self.set_total_value()
self.set_stock_items_cost()
self.calculate_total_repair_cost()
def update_status(self):
@@ -26,7 +26,7 @@ class AssetRepair(AccountsController):
else:
self.asset_doc.set_status()
def set_total_value(self):
def set_stock_items_cost(self):
for item in self.get("stock_items"):
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
@@ -66,6 +66,7 @@ class AssetRepair(AccountsController):
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
self.db_set("stock_entry", None)
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
@@ -133,6 +134,7 @@ class AssetRepair(AccountsController):
"qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate,
"serial_no": stock_item.serial_no,
"cost_center": self.cost_center,
},
)
@@ -142,72 +144,42 @@ class AssetRepair(AccountsController):
self.db_set("stock_entry", stock_entry.name)
def increase_stock_quantity(self):
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
stock_entry.flags.ignore_links = True
stock_entry.cancel()
if self.stock_entry:
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
stock_entry.flags.ignore_links = True
stock_entry.cancel()
def make_gl_entries(self, cancel=False):
if flt(self.repair_cost) > 0:
if flt(self.total_repair_cost) > 0:
gl_entries = self.get_gl_entries()
make_gl_entries(gl_entries, cancel)
def get_gl_entries(self):
gl_entries = []
repair_and_maintenance_account = frappe.db.get_value(
"Company", self.company, "repair_and_maintenance_account"
)
fixed_asset_account = get_asset_account(
"fixed_asset_account", asset=self.asset, company=self.company
)
expense_account = (
self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account)
self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account)
return gl_entries
def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account):
if flt(self.repair_cost) <= 0:
return
pi_expense_account = (
frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
)
gl_entries.append(
self.get_gl_dict(
{
"account": expense_account,
"credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost,
"against": repair_and_maintenance_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company,
},
item=self,
)
)
if self.get("stock_consumption"):
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
for item in stock_entry.items:
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account,
"credit": item.amount,
"credit_in_account_currency": item.amount,
"against": repair_and_maintenance_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company,
},
item=self,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": fixed_asset_account,
"debit": self.total_repair_cost,
"debit_in_account_currency": self.total_repair_cost,
"against": expense_account,
"debit": self.repair_cost,
"debit_in_account_currency": self.repair_cost,
"against": pi_expense_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
@@ -220,7 +192,75 @@ class AssetRepair(AccountsController):
)
)
return gl_entries
gl_entries.append(
self.get_gl_dict(
{
"account": pi_expense_account,
"credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost,
"against": fixed_asset_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company,
},
item=self,
)
)
def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account):
if not (self.get("stock_consumption") and self.get("stock_items")):
return
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
default_expense_account = None
if not erpnext.is_perpetual_inventory_enabled(self.company):
default_expense_account = frappe.get_cached_value(
"Company", self.company, "default_expense_account"
)
if not default_expense_account:
frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company))
for item in stock_entry.items:
if flt(item.amount) > 0:
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account or default_expense_account,
"credit": item.amount,
"credit_in_account_currency": item.amount,
"against": fixed_asset_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company,
},
item=self,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": fixed_asset_account,
"debit": item.amount,
"debit_in_account_currency": item.amount,
"against": item.expense_account or default_expense_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"against_voucher_type": "Stock Entry",
"against_voucher": self.stock_entry,
"company": self.company,
},
item=self,
)
)
def modify_depreciation_schedule(self):
for row in self.asset_doc.finance_books:

View File

@@ -6,6 +6,7 @@ import unittest
import frappe
from frappe.utils import flt, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_account
from erpnext.assets.doctype.asset.test_asset import (
create_asset,
create_asset_data,
@@ -125,10 +126,109 @@ class TestAssetRepair(unittest.TestCase):
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
self.assertTrue(asset_repair.purchase_invoice)
def test_gl_entries(self):
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
gl_entry = frappe.get_last_doc("GL Entry")
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
def test_gl_entries_with_perpetual_inventory(self):
set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
asset_category = frappe.get_doc("Asset Category", "Computers")
asset_category.append(
"accounts",
{
"company_name": "_Test Company with perpetual inventory",
"fixed_asset_account": "_Test Fixed Asset - TCP1",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
"depreciation_expense_account": "_Test Depreciations - TCP1",
},
)
asset_category.save()
asset_repair = create_asset_repair(
capitalize_repair_cost=1,
stock_consumption=1,
warehouse="Stores - TCP1",
company="_Test Company with perpetual inventory",
submit=1,
)
gl_entries = frappe.db.sql(
"""
select
account,
sum(debit) as debit,
sum(credit) as credit
from `tabGL Entry`
where
voucher_type='Asset Repair'
and voucher_no=%s
group by
account
""",
asset_repair.name,
as_dict=1,
)
self.assertTrue(gl_entries)
fixed_asset_account = get_asset_account(
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
)
pi_expense_account = (
frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
)
stock_entry_expense_account = (
frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account
)
expected_values = {
fixed_asset_account: [asset_repair.total_repair_cost, 0],
pi_expense_account: [0, asset_repair.repair_cost],
stock_entry_expense_account: [0, 100],
}
for d in gl_entries:
self.assertEqual(expected_values[d.account][0], d.debit)
self.assertEqual(expected_values[d.account][1], d.credit)
def test_gl_entries_with_periodical_inventory(self):
frappe.db.set_value(
"Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC"
)
asset_repair = create_asset_repair(
capitalize_repair_cost=1,
stock_consumption=1,
submit=1,
)
gl_entries = frappe.db.sql(
"""
select
account,
sum(debit) as debit,
sum(credit) as credit
from `tabGL Entry`
where
voucher_type='Asset Repair'
and voucher_no=%s
group by
account
""",
asset_repair.name,
as_dict=1,
)
self.assertTrue(gl_entries)
fixed_asset_account = get_asset_account(
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
)
default_expense_account = frappe.get_cached_value(
"Company", asset_repair.company, "default_expense_account"
)
expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]}
for d in gl_entries:
self.assertEqual(expected_values[d.account][0], d.debit)
self.assertEqual(expected_values[d.account][1], d.credit)
def test_increase_in_asset_life(self):
asset = create_asset(calculate_depreciation=1, submit=1)
@@ -160,7 +260,7 @@ def create_asset_repair(**args):
if args.asset:
asset = args.asset
else:
asset = create_asset(is_existing_asset=1, submit=1)
asset = create_asset(is_existing_asset=1, submit=1, company=args.company)
asset_repair = frappe.new_doc("Asset Repair")
asset_repair.update(
{
@@ -192,7 +292,7 @@ def create_asset_repair(**args):
if args.submit:
asset_repair.repair_status = "Completed"
asset_repair.cost_center = "_Test Cost Center - _TC"
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
if args.stock_consumption:
stock_entry = frappe.get_doc(
@@ -204,6 +304,8 @@ def create_asset_repair(**args):
"t_warehouse": asset_repair.warehouse,
"item_code": asset_repair.stock_items[0].item_code,
"qty": asset_repair.stock_items[0].consumed_quantity,
"basic_rate": args.rate if args.get("rate") is not None else 100,
"cost_center": asset_repair.cost_center,
},
)
stock_entry.submit()
@@ -213,7 +315,13 @@ def create_asset_repair(**args):
asset_repair.repair_cost = 1000
if asset.calculate_depreciation:
asset_repair.increase_in_asset_life = 12
asset_repair.purchase_invoice = make_purchase_invoice().name
pi = make_purchase_invoice(
company=asset.company,
expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
cost_center=asset_repair.cost_center,
warehouse=asset_repair.warehouse,
)
asset_repair.purchase_invoice = pi.name
asset_repair.submit()
return asset_repair

View File

@@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
let d = locals[cdt][cdn];
return {
query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts",
filters: {'supplier': d.supplier}
}
query: "frappe.contacts.doctype.contact.contact.contact_query",
filters: {
link_doctype: "Supplier",
link_name: d.supplier || ""
}
};
}
},

View File

@@ -286,18 +286,6 @@ def get_list_context(context=None):
return list_context
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
limit %(page_len)s offset %(start)s""",
{"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
)
@frappe.whitelist()
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
def postprocess(source, target_doc):

View File

@@ -4,6 +4,8 @@
# Decompiled by https://python-decompiler.com
import copy
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -11,10 +13,12 @@ from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_
execute,
)
from erpnext.controllers.tests.test_subcontracting_controller import (
get_rm_items,
get_subcontracting_order,
make_service_item,
make_stock_in_entry,
make_stock_transfer_entry,
)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_subcontracting_receipt,
)
@@ -36,15 +40,18 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
sco = get_subcontracting_order(
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
)
make_stock_entry(
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
)
make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse 1 - _TC",
qty=100,
basic_rate=100,
rm_items = get_rm_items(sco.supplied_items)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for item in rm_items:
item["sco_rm_detail"] = sco.items[0].name
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
make_subcontracting_receipt_against_sco(sco.name)
sco.reload()
col, data = execute(

View File

@@ -0,0 +1,105 @@
# Version 14.0.0 Release Notes
### Accounting
- [Improved Indian Compliance and GST APIs](https://docs.erpnext.com/docs/v14/user/manual/en/regional/india)
- [Common Party Accounting](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/common_party_accounting)
- [Provisional accounting for expenses](https://github.com/frappe/erpnext/pull/29451)
- [Discount Accounting](https://github.com/frappe/erpnext/pull/26359)
- [New Payment Reconciliation Tool](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/payment-reconciliation)
- [Coupon Code in POS](https://github.com/frappe/erpnext/pull/27004)
- [Configurable cost center allocation](https://docs.erpnext.com/docs/v14/user/manual/en/cost_center_allocation)
- [Payment Ledger](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/payment_ledger)
- [Cash and Non trade discounts in Sales Invoice](https://github.com/frappe/erpnext/pull/31405)
- [Improved TaxJar Integration](https://docs.erpnext.com/docs/v14/user/manual/en/erpnext_integration/taxjar_integration)
- [KSA E-Invoicing](https://docs.erpnext.com/docs/v14/user/manual/en/simplified_ksa_vat_management_and_reporting)
- [South Africa VAT Audit Report](https://docs.erpnext.com/docs/v14/user/manual/en/regional/south_africa/vat_audit_report)
- [E Invoice Eway Bill Distance is calculated automatically](https://github.com/frappe/erpnext/pull/30908)
- [Payment Terms Status report](https://github.com/frappe/erpnext/pull/29137)
- [Merge POS invoices based on customer group](https://github.com/frappe/erpnext/pull/27471)
- [Ledger Merger](https://github.com/frappe/erpnext/pull/28812)
- [Increase number of supported currency exchanges](https://github.com/frappe/erpnext/pull/26763)
### Stock
- [LIFO Valuation](https://github.com/frappe/erpnext/pull/29296)
- [Batch-wise Valuation Rates](https://github.com/frappe/erpnext/pull/29804)
- [Better Barcode Scanning](https://github.com/frappe/erpnext/pull/30516)
- [Over transfer allowance for material transfers](https://github.com/frappe/erpnext/pull/26264)
- [Scanning in Pick List](https://github.com/frappe/erpnext/pull/30832)
- [GLE reposting with progress and chunking for backdated entries](https://github.com/frappe/erpnext/pull/31343)
### E-Commerce
- [Redesigned E-commerce Portal](https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce)
- [E-commerce Search](https://docs.erpnext.com/docs/v14/user/manual/en/e_commerce/e_commerce_search)
### Assets
- [Asset Splitting](https://github.com/frappe/erpnext/pull/29350)
- [Grouped Asset](https://github.com/frappe/erpnext/pull/29334)
- [Asset Repair](https://github.com/frappe/erpnext/pull/25798)
- [Consume serialized items during Asset Repair](https://github.com/frappe/erpnext/pull/28349)
### Manufacturing
- [Faster BOM Update Tool](https://github.com/frappe/erpnext/pull/31078)
- [Scrap Item in Job Card](https://github.com/frappe/erpnext/pull/27518)
- [Process Loss in manufacturing](https://github.com/frappe/erpnext/pull/26151)
- [Production Plan Summary Report](https://github.com/frappe/erpnext/pull/26240)
- [Work Order Consumed Materials Report](https://github.com/frappe/erpnext/pull/28500)
- [Provision to close the Work Order](https://github.com/frappe/erpnext/pull/28150)
- [Provision to aggregate subassembly items in production plan](https://github.com/frappe/erpnext/pull/28939)
### Subcontracting
- [New Subcontracting Module](https://github.com/frappe/erpnext/pull/30955)
- [Subcontracted Purchase Order from the Production Plan](https://github.com/frappe/erpnext/pull/26240)
### CRM
- [Refreshed CRM Flows](https://github.com/frappe/erpnext/pull/31311)
- [New Prospect document](https://github.com/frappe/erpnext/pull/27102)
- [CRM Settings Page](https://docs.erpnext.com/docs/v13/user/manual/en/CRM/crm_settings)
- [Competitor Tagging in Opportunity and Quotation](https://github.com/frappe/erpnext/pull/28050)
- [Sales Pipeline Analytics Report](https://github.com/frappe/erpnext/pull/26639)
- [Opportunity Summary by Sales Stage Report](https://github.com/frappe/erpnext/pull/26639)
### HR & Payroll
- [Organizational Chart](https://github.com/frappe/erpnext/pull/26261)
- [Full and Final Settlement](https://github.com/frappe/erpnext/pull/26364)
- [Income tax computation Report](https://github.com/frappe/erpnext/pull/29963)
- [Employee Grievance](https://github.com/frappe/erpnext/pull/25705)
- [Tax for recurring additional salary](https://github.com/frappe/erpnext/pull/27459)
- [Tracking Multi-round interview](https://github.com/frappe/erpnext/pull/25482)
- [Exit Interview and Employee Exits Report](https://github.com/frappe/erpnext/pull/28741)
- [Leave Type configuration to allow over allocation](https://github.com/frappe/erpnext/pull/30940)
- [Employee Reminders](https://github.com/frappe/erpnext/pull/25735)
- [Refactored Employee Leave Balance](https://github.com/frappe/erpnext/pull/29439)
### Healthcare
- [Treatment Plan Template](https://github.com/frappe/erpnext/pull/26557)
- [Capacity for Service Unit, concurrent appointments based on capacity, Patient Appointments](https://github.com/frappe/erpnext/pull/27219)
- [UOM specific barcode](https://docs.erpnext.com/docs/v14/user/manual/en/stock/articles/track-items-using-barcode#uom-specific-barcode)
- [Redesigned Patient History and Patient Progress](https://github.com/frappe/erpnext/pull/27100)
### New apps
The following modules has been separated out from ERPNext and new apps has been created.
- [HR and Payroll](https://github.com/frappe/hrms)
- [Healthcare](https://github.com/frappe/health)
- [Education](https://github.com/frappe/education)
- [E-commerce Integration](https://github.com/frappe/ecommerce_integrations)
- [Hospitality](https://github.com/frappe/hospitality)
- [Non-Profit](https://github.com/frappe/non_profit)
- [Agriculture](https://github.com/frappe/agriculture)
- [Datev Integration](https://github.com/alyf-de/erpnext_datev)
- [Germany Localisation](https://github.com/alyf-de/erpnext_germany)
### Others
- [Unicommerce Integration](https://docs.erpnext.com/docs/v13/user/manual/en/erpnext_integration/unicommerce_integration)
- [Bulk Transaction Processing](https://github.com/frappe/erpnext/pull/28580)
- [Refactored Document Naming Settings](https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/settings/document-naming-settings)
- [Project Portal Enhancements](https://github.com/frappe/erpnext/pull/26090)
- [Refund entry against loans](https://github.com/frappe/erpnext/pull/29460)
- [Bank Reconciliation for loan documents](https://github.com/frappe/erpnext/pull/29865)

View File

@@ -1109,17 +1109,17 @@ class AccountsController(TransactionBase):
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
)
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit"
rev_dr_cr = "debit"
supplier_or_customer = self.supplier
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
supplier_or_customer = self.customer
if enable_discount_accounting:
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit"
rev_dr_cr = "debit"
supplier_or_customer = self.supplier
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
supplier_or_customer = self.customer
for item in self.get("items"):
if item.get("discount_amount") and item.get("discount_account"):
discount_amount = item.discount_amount * item.qty
@@ -1173,18 +1173,22 @@ class AccountsController(TransactionBase):
)
)
if self.get("discount_amount") and self.get("additional_discount_account"):
gl_entries.append(
self.get_gl_dict(
{
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.discount_amount,
"cost_center": self.cost_center,
},
item=self,
)
if (
(enable_discount_accounting or self.get("is_cash_or_non_trade_discount"))
and self.get("additional_discount_account")
and self.get("discount_amount")
):
gl_entries.append(
self.get_gl_dict(
{
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.discount_amount,
"cost_center": self.cost_center,
},
item=self,
)
)
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for

View File

@@ -86,6 +86,7 @@ class BuyingController(SubcontractingController):
company=self.company,
party_address=self.get("supplier_address"),
shipping_address=self.get("shipping_address"),
company_address=self.get("billing_address"),
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
ignore_permissions=self.flags.ignore_permissions,
)

View File

@@ -18,8 +18,9 @@ from erpnext.stock.get_item_details import _get_item_tax_template
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Employee"
conditions = []
fields = get_fields("Employee", ["name", "employee_name"])
fields = get_fields(doctype, ["name", "employee_name"])
return frappe.db.sql(
"""select {fields} from `tabEmployee`
@@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
doctype = "Lead"
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
return frappe.db.sql(
"""select {fields} from `tabLead`
@@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Customer"
conditions = []
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
@@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
else:
fields = ["name", "customer_name", "customer_group", "territory"]
fields = get_fields("Customer", fields)
fields = get_fields(doctype, fields)
searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
return frappe.db.sql(
@@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Supplier"
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
if supp_master_name == "Supplier Name":
@@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
else:
fields = ["name", "supplier_name", "supplier_group"]
fields = get_fields("Supplier", fields)
fields = get_fields(doctype, fields)
return frappe.db.sql(
"""select {field} from `tabSupplier`
@@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Account"
company_currency = erpnext.get_company_currency(filters.get("company"))
def get_accounts(with_account_type_filter):
@@ -197,13 +202,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
doctype = "Item"
conditions = []
if isinstance(filters, str):
filters = json.loads(filters)
# Get searchfields from meta and use in Item Link field query
meta = frappe.get_meta("Item", cached=True)
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
# these are handled separately
@@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
filters.pop("supplier", None)
description_cond = ""
if frappe.db.count("Item", cache=True) < 50000:
if frappe.db.count(doctype, cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = "or tabItem.description LIKE %(txt)s"
return frappe.db.sql(
@@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def bom(doctype, txt, searchfield, start, page_len, filters):
doctype = "BOM"
conditions = []
fields = get_fields("BOM", ["name", "item"])
fields = get_fields(doctype, ["name", "item"])
return frappe.db.sql(
"""select {fields}
@@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
doctype = "Project"
cond = ""
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
@@ -338,8 +346,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
frappe.db.escape(filters.get("customer"))
)
fields = get_fields("Project", ["name", "project_name"])
searchfields = frappe.get_meta("Project").get_search_fields()
fields = get_fields(doctype, ["name", "project_name"])
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
return frappe.db.sql(
@@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
doctype = "Delivery Note"
fields = get_fields(doctype, ["name", "customer", "posting_date"])
return frappe.db.sql(
"""
@@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
doctype = "Batch"
cond = ""
if filters.get("posting_date"):
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
@@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if filters.get("is_return"):
having_clause = ""
meta = frappe.get_meta("Batch", cached=True)
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
search_columns = ""
@@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
doctype = "Account"
filter_list = []
if isinstance(filters, dict):
@@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute(
"Account",
doctype,
filters=filter_list,
fields=["name", "parent_account"],
limit_start=start,
@@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
if not filters:
filters = {}
doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
if not filters:
filters = {}
doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
# Should be used when item code is passed in filters.
doctype = "Warehouse"
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)

View File

@@ -36,6 +36,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError):
pass
class BatchExpiredError(frappe.ValidationError):
pass
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
@@ -77,6 +81,10 @@ class StockController(AccountsController):
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
is_material_issue = False
if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
is_material_issue = True
for d in self.get("items"):
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
serial_nos = frappe.get_all(
@@ -93,6 +101,9 @@ class StockController(AccountsController):
)
)
if is_material_issue:
continue
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
@@ -100,7 +111,8 @@ class StockController(AccountsController):
frappe.throw(
_("Row #{0}: The batch {1} has already expired.").format(
d.idx, get_link_to_form("Batch", d.get("batch_no"))
)
),
BatchExpiredError,
)
def clean_serial_nos(self):
@@ -310,7 +322,13 @@ class StockController(AccountsController):
)
if (
self.doctype
not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry")
not in (
"Purchase Receipt",
"Purchase Invoice",
"Stock Reconciliation",
"Stock Entry",
"Subcontracting Receipt",
)
and not is_expense_account
):
frappe.throw(
@@ -374,9 +392,24 @@ class StockController(AccountsController):
def update_inventory_dimensions(self, row, sl_dict) -> None:
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
for dimension in dimensions:
if dimension and row.get(dimension.source_fieldname):
if not dimension:
continue
if row.get(dimension.source_fieldname):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
sl_dict[dimension.target_fieldname] = self.get(dimension.fetch_from_parent)
# 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"
)
if fieldname and self.get(fieldname):
sl_dict[dimension.target_fieldname] = self.get(fieldname)
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries

View File

@@ -490,7 +490,7 @@ class SubcontractingController(StockController):
row.item_code,
row.get(self.subcontract_data.order_field),
) and transfer_item.qty > 0:
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
qty = flt(self.__get_qty_based_on_material_transfer(row, transfer_item))
transfer_item.qty -= qty
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
@@ -720,6 +720,25 @@ class SubcontractingController(StockController):
sco_doc = frappe.get_doc("Subcontracting Order", sco)
sco_doc.update_status()
def set_missing_values_in_additional_costs(self):
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
if self.total_additional_costs:
if self.distribute_additional_costs_based_on == "Amount":
total_amt = sum(flt(item.amount) for item in self.get("items"))
for item in self.items:
item.additional_cost_per_qty = (
(item.amount * self.total_additional_costs) / total_amt
) / item.qty
else:
total_qty = sum(flt(item.qty) for item in self.get("items"))
additional_cost_per_qty = self.total_additional_costs / total_qty
for item in self.items:
item.additional_cost_per_qty = additional_cost_per_qty
else:
for item in self.items:
item.additional_cost_per_qty = 0
@frappe.whitelist()
def get_current_stock(self):
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
@@ -730,7 +749,7 @@ class SubcontractingController(StockController):
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
"actual_qty",
)
item.current_stock = flt(actual_qty) or 0
item.current_stock = flt(actual_qty)
@property
def sub_contracted_items(self):

View File

@@ -37,6 +37,12 @@ class calculate_taxes_and_totals(object):
self.set_discount_amount()
self.apply_discount_amount()
# Update grand total as per cash and non trade discount
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
self.doc.grand_total -= self.doc.discount_amount
self.doc.base_grand_total -= self.doc.base_discount_amount
self.set_rounded_total()
self.calculate_shipping_charges()
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
@@ -500,9 +506,6 @@ class calculate_taxes_and_totals(object):
else:
self.doc.grand_total = flt(self.doc.net_total)
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
self.doc.grand_total -= self.doc.discount_amount
if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
@@ -597,16 +600,16 @@ class calculate_taxes_and_totals(object):
if not self.doc.apply_discount_on:
frappe.throw(_("Please select Apply Discount On"))
self.doc.base_discount_amount = flt(
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
)
if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
"is_cash_or_non_trade_discount"
):
self.discount_amount_applied = True
return
self.doc.base_discount_amount = flt(
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
)
total_for_discount_amount = self.get_total_for_discount_amount()
taxes = self.doc.get("taxes")
net_total = 0

View File

@@ -36,6 +36,36 @@ class TestSubcontractingController(FrappeTestCase):
sco.remove_empty_rows()
self.assertEqual((len_before - 1), len(sco.service_items))
def test_set_missing_values_in_additional_costs(self):
sco = get_subcontracting_order(do_not_submit=1)
rate_without_additional_cost = sco.items[0].rate
amount_without_additional_cost = sco.items[0].amount
additional_amount = 120
sco.append(
"additional_costs",
{
"expense_account": "Cost of Goods Sold - _TC",
"description": "Test",
"amount": additional_amount,
},
)
sco.save()
additional_cost_per_qty = additional_amount / sco.items[0].qty
self.assertEqual(sco.items[0].additional_cost_per_qty, additional_cost_per_qty)
self.assertEqual(rate_without_additional_cost + additional_cost_per_qty, sco.items[0].rate)
self.assertEqual(amount_without_additional_cost + additional_amount, sco.items[0].amount)
sco.additional_costs = []
sco.save()
self.assertEqual(sco.items[0].additional_cost_per_qty, 0)
self.assertEqual(rate_without_additional_cost, sco.items[0].rate)
self.assertEqual(amount_without_additional_cost, sco.items[0].amount)
def test_create_raw_materials_supplied(self):
sco = get_subcontracting_order()
sco.supplied_items = None

View File

@@ -340,8 +340,8 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No of Employees",
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
},
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
@@ -514,7 +514,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2022-07-22 15:55:03.176094",
"modified": "2022-08-09 18:26:17.101521",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",

View File

@@ -463,7 +463,7 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No of Employees",
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "annual_revenue",
@@ -622,7 +622,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
"modified": "2022-07-22 18:46:32.858696",
"modified": "2022-08-09 18:26:37.235964",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",

View File

@@ -82,7 +82,7 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No. of Employees",
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "annual_revenue",
@@ -218,7 +218,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-06-22 15:10:26.887502",
"modified": "2022-08-09 18:26:56.950185",
"modified_by": "Administrator",
"module": "CRM",
"name": "Prospect",

View File

@@ -12,7 +12,9 @@ from decimal import Decimal
import frappe
from bs4 import BeautifulSoup as bs
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.custom.doctype.custom_field.custom_field import (
create_custom_fields as _create_custom_fields,
)
from frappe.model.document import Document
from frappe.utils.data import format_datetime
@@ -577,22 +579,25 @@ class TallyMigration(Document):
new_year.save()
oldest_year = new_year
def create_custom_fields(doctypes):
tally_guid_df = {
"fieldtype": "Data",
"fieldname": "tally_guid",
"read_only": 1,
"label": "Tally GUID",
}
tally_voucher_no_df = {
"fieldtype": "Data",
"fieldname": "tally_voucher_no",
"read_only": 1,
"label": "Tally Voucher Number",
}
for df in [tally_guid_df, tally_voucher_no_df]:
for doctype in doctypes:
create_custom_field(doctype, df)
def create_custom_fields():
_create_custom_fields(
{
("Journal Entry", "Purchase Invoice", "Sales Invoice"): [
{
"fieldtype": "Data",
"fieldname": "tally_guid",
"read_only": 1,
"label": "Tally GUID",
},
{
"fieldtype": "Data",
"fieldname": "tally_voucher_no",
"read_only": 1,
"label": "Tally Voucher Number",
},
]
}
)
def create_price_list():
frappe.get_doc(
@@ -628,7 +633,7 @@ class TallyMigration(Document):
create_fiscal_years(vouchers)
create_price_list()
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
create_custom_fields()
total = len(vouchers)
is_last = False

View File

@@ -6,7 +6,7 @@ from urllib.parse import urlparse
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.model.document import Document
from frappe.utils.nestedset import get_root_of
@@ -19,27 +19,24 @@ class WoocommerceSettings(Document):
def create_delete_custom_fields(self):
if self.enable_sync:
custom_fields = {}
# create
for doctype in ["Customer", "Sales Order", "Item", "Address"]:
df = dict(
fieldname="woocommerce_id",
label="Woocommerce ID",
fieldtype="Data",
read_only=1,
print_hide=1,
)
create_custom_field(doctype, df)
for doctype in ["Customer", "Address"]:
df = dict(
fieldname="woocommerce_email",
label="Woocommerce Email",
fieldtype="Data",
read_only=1,
print_hide=1,
)
create_custom_field(doctype, df)
create_custom_fields(
{
("Customer", "Sales Order", "Item", "Address"): dict(
fieldname="woocommerce_id",
label="Woocommerce ID",
fieldtype="Data",
read_only=1,
print_hide=1,
),
("Customer", "Address"): dict(
fieldname="woocommerce_email",
label="Woocommerce Email",
fieldtype="Data",
read_only=1,
print_hide=1,
),
}
)
if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}):
item_group = frappe.new_doc("Item Group")

View File

@@ -26,6 +26,7 @@ def handle_incoming_call(**kwargs):
except Exception as e:
frappe.db.rollback()
exotel_settings.log_error("Error in Exotel incoming call")
frappe.db.commit()
@frappe.whitelist(allow_guest=True)

View File

@@ -507,6 +507,7 @@ accounting_dimension_doctypes = [
"Shipping Rule",
"Landed Cost Item",
"Asset Value Adjustment",
"Asset Repair",
"Loyalty Program",
"Stock Reconciliation",
"POS Profile",
@@ -519,6 +520,10 @@ accounting_dimension_doctypes = [
"Purchase Order",
"Purchase Receipt",
"Sales Order",
"Subcontracting Order",
"Subcontracting Order Item",
"Subcontracting Receipt",
"Subcontracting Receipt Item",
]
# get matching queries for Bank Reconciliation

View File

@@ -135,7 +135,11 @@ def calculate_accrual_amount_for_demand_loans(
def make_accrual_interest_entry_for_demand_loans(
posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"
):
query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1}
query_filters = {
"status": ("in", ["Disbursed", "Partially Disbursed"]),
"docstatus": 1,
"is_term_loan": 0,
}
if loan_type:
query_filters.update({"loan_type": loan_type})
@@ -229,6 +233,7 @@ def get_term_loans(date, term_loan=None, loan_type=None):
AND l.is_term_loan =1
AND rs.payment_date <= %s
AND rs.is_accrued=0 {0}
AND rs.interest_amount > 0
AND l.status = 'Disbursed'
ORDER BY rs.payment_date""".format(
condition

View File

@@ -415,7 +415,7 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
},
"Maintenance Schedule Item": {
"doctype": "Maintenance Visit Purpose",
"condition": lambda doc: doc.item_name == item_name,
"condition": lambda doc: doc.item_name == item_name if item_name else True,
"field_map": {"sales_person": "service_person"},
"postprocess": update_serial,
},

View File

@@ -189,8 +189,8 @@ class BOM(WebsiteGenerator):
self.validate_transfer_against()
self.set_routing_operations()
self.validate_operations()
self.update_exploded_items(save=False)
self.calculate_cost()
self.update_exploded_items(save=False)
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
self.validate_scrap_items()

View File

@@ -611,6 +611,34 @@ class TestBOM(FrappeTestCase):
bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
def test_exploded_items_rate(self):
rm_item = make_item(
properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
).name
fg_item = make_item(properties={"is_stock_item": 1}).name
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True)
bom.rm_cost_as_per = "Last Purchase Rate"
bom.save()
self.assertEqual(bom.items[0].base_rate, 89)
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
bom.rm_cost_as_per = "Price List"
bom.save()
self.assertEqual(bom.items[0].base_rate, 0.0)
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
bom.rm_cost_as_per = "Valuation Rate"
bom.save()
self.assertEqual(bom.items[0].base_rate, 99)
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
bom.submit()
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -184,6 +184,7 @@
"in_list_view": 1,
"label": "Rate",
"options": "currency",
"read_only": 1,
"reqd": 1
},
{
@@ -288,7 +289,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-05-19 02:32:43.785470",
"modified": "2022-07-28 10:20:51.559010",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",

View File

@@ -482,7 +482,6 @@ class ProductionPlan(Document):
"bom_no",
"stock_uom",
"bom_level",
"production_plan_item",
"schedule_date",
]:
if row.get(field):
@@ -639,6 +638,9 @@ class ProductionPlan(Document):
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
if not row.item_code:
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
@@ -654,6 +656,8 @@ class ProductionPlan(Document):
row.idx = idx + 1
self.append("sub_assembly_items", row)
self.set_default_supplier_for_subcontracting_order()
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
"Modify bom_data, set additional details."
for data in bom_data:
@@ -665,6 +669,32 @@ class ProductionPlan(Document):
"Subcontract" if data.is_sub_contracted_item else "In House"
)
def set_default_supplier_for_subcontracting_order(self):
items = [
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
]
if not items:
return
default_supplier = frappe._dict(
frappe.get_all(
"Item Default",
fields=["parent", "default_supplier"],
filters={"parent": ("in", items), "default_supplier": ("is", "set")},
as_list=1,
)
)
if not default_supplier:
return
for row in self.sub_assembly_items:
if row.type_of_manufacturing != "Subcontract":
continue
row.supplier = default_supplier.get(row.production_item)
def combine_subassembly_items(self, sub_assembly_items_store):
"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
key_wise_data = {}

View File

@@ -11,8 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_warehouse_list,
)
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
@@ -280,6 +281,31 @@ class TestProductionPlan(FrappeTestCase):
pln.reload()
pln.cancel()
def test_production_plan_subassembly_default_supplier(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree_1 = {"Test Laptop": {"Test Motherboard": {"Test Motherboard Wires": {}}}}
bom = create_nested_bom(bom_tree_1, prefix="")
item_doc = frappe.get_doc("Item", "Test Motherboard")
company = "_Test Company"
item_doc.is_sub_contracted_item = 1
for row in item_doc.item_defaults:
if row.company == company and not row.default_supplier:
row.default_supplier = "_Test Supplier"
if not item_doc.item_defaults:
item_doc.append("item_defaults", {"company": company, "default_supplier": "_Test Supplier"})
item_doc.save()
plan = create_production_plan(item_code="Test Laptop", use_multi_level_bom=1, do_not_submit=True)
plan.get_sub_assembly_items()
plan.set_default_supplier_for_subcontracting_order()
self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
def test_production_plan_combine_subassembly(self):
"""
Test combining Sub assembly items belonging to the same BOM in Prod Plan.
@@ -583,9 +609,6 @@ class TestProductionPlan(FrappeTestCase):
Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
"""
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_se_from_wo,
)
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -629,9 +652,6 @@ class TestProductionPlan(FrappeTestCase):
def test_production_plan_pending_qty_independent_items(self):
"Test Prod Plan impact if items are added independently (no from SO or MR)."
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_se_from_wo,
)
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -728,6 +748,57 @@ class TestProductionPlan(FrappeTestCase):
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
def test_produced_qty_for_multi_level_bom_item(self):
# Create Items and BOMs
rm_item = make_item(properties={"is_stock_item": 1}).name
sub_assembly_item = make_item(properties={"is_stock_item": 1}).name
fg_item = make_item(properties={"is_stock_item": 1}).name
make_stock_entry(
item_code=rm_item,
qty=60,
to_warehouse="Work In Progress - _TC",
rate=99,
purpose="Material Receipt",
)
make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3)
make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4)
# Step - 1: Create Production Plan
pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1)
pln.get_sub_assembly_items()
# Step - 2: Create Work Orders
pln.make_work_order()
work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name")
sa_wo = fg_wo = None
for work_order in work_orders:
wo_doc = frappe.get_doc("Work Order", work_order)
if wo_doc.production_plan_item:
wo_doc.update(
{"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
)
fg_wo = wo_doc.name
else:
wo_doc.update(
{"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"}
)
sa_wo = wo_doc.name
wo_doc.submit()
# Step - 3: Complete Work Orders
se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture"))
se.submit()
se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture"))
se.submit()
# Step - 4: Check Production Plan Item Produced Qty
pln.load_from_db()
self.assertEqual(pln.status, "Completed")
self.assertEqual(pln.po_items[0].produced_qty, 5)
def create_production_plan(**args):
"""

View File

@@ -26,6 +26,8 @@ from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin
test_dependencies = ["BOM"]
class TestWorkOrder(FrappeTestCase):
def setUp(self):

View File

@@ -7,6 +7,6 @@ def get_data():
"non_standard_fieldnames": {"Batch": "reference_name"},
"transactions": [
{"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]},
{"label": _("Reference"), "items": ["Serial No", "Batch"]},
{"label": _("Reference"), "items": ["Serial No", "Batch", "Material Request"]},
],
}

View File

@@ -268,6 +268,7 @@ erpnext.patches.v13_0.enable_ksa_vat_docs #1
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
[post_model_sync]
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
@@ -308,4 +309,7 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.crm_ux_cleanup
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger

View File

@@ -14,7 +14,8 @@ def execute():
for sla in frappe.get_all("Service Level Agreement"):
agreement = frappe.get_doc("Service Level Agreement", sla.name)
agreement.document_type = "Issue"
agreement.db_set("document_type", "Issue")
agreement.reload()
agreement.apply_sla_for_resolution = 1
agreement.append("sla_fulfilled_on", {"status": "Resolved"})
agreement.append("sla_fulfilled_on", {"status": "Closed"})

View File

@@ -0,0 +1,29 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
doctype = "Asset Repair"
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -0,0 +1,47 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
count = 1
for d in accounting_dimensions:
if count % 2 == 0:
insert_after_field = "dimension_col_break"
else:
insert_after_field = "accounting_dimensions_section"
for doctype in [
"Subcontracting Order",
"Subcontracting Order Item",
"Subcontracting Receipt",
"Subcontracting Receipt Item",
]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": insert_after_field,
}
try:
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)
except Exception:
pass
count += 1

View File

@@ -0,0 +1,26 @@
import frappe
def execute():
options = {
"11-20": "11-50",
"21-30": "11-50",
"31-100": "51-200",
"101-500": "201-500",
"500-1000": "501-1000",
">1000": "1000+",
}
for doctype in ("Lead", "Opportunity", "Prospect"):
frappe.reload_doctype(doctype)
for key, value in options.items():
frappe.db.sql(
"""
update `tab{doctype}`
set no_of_employees = %s
where no_of_employees = %s
""".format(
doctype=doctype
),
(value, key),
)

View File

@@ -0,0 +1,56 @@
import frappe
from frappe import qb
from frappe.utils import create_batch
def execute():
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
gle = qb.DocType("GL Entry")
ple = qb.DocType("Payment Ledger Entry")
# get ple and their remarks from GL Entry
pl_entries = (
qb.from_(ple)
.left_join(gle)
.on(
(ple.account == gle.account)
& (ple.party_type == gle.party_type)
& (ple.party == gle.party)
& (ple.voucher_type == gle.voucher_type)
& (ple.voucher_no == gle.voucher_no)
& (ple.company == gle.company)
)
.select(
ple.company,
ple.account,
ple.party_type,
ple.party,
ple.voucher_type,
ple.voucher_no,
gle.remarks.as_("gle_remarks"),
)
.where((ple.delinked == 0) & (gle.is_cancelled == 0))
.run(as_dict=True)
)
if pl_entries:
# split into multiple batches, update and commit for each batch
batch_size = 1000
for batch in create_batch(pl_entries, batch_size):
for entry in batch:
query = (
qb.update(ple)
.set(ple.remarks, entry.gle_remarks)
.where(
(ple.company == entry.company)
& (ple.account == entry.account)
& (ple.party_type == entry.party_type)
& (ple.party == entry.party)
& (ple.voucher_type == entry.voucher_type)
& (ple.voucher_no == entry.voucher_no)
)
)
query.run()
frappe.db.commit()

View File

@@ -57,7 +57,11 @@ class TestHomepageSection(unittest.TestCase):
self.assertEqual(cards[0].h5.text, "Card 1")
self.assertEqual(cards[0].a["href"], "/card-1")
self.assertEqual(cards[1].p.text, "Subtitle 2")
self.assertEqual(cards[1].find(class_="website-image-lazy")["data-src"], "test.jpg")
img = cards[1].find(class_="card-img-top")
self.assertEqual(img["src"], "test.jpg")
self.assertEqual(img["loading"], "lazy")
# cleanup
frappe.db.rollback()

View File

@@ -379,7 +379,7 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
{fcond} {mcond}
order by
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
(case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end)
(case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end),
idx desc,
name, full_name
limit %(page_len)s offset %(start)s""".format(

View File

@@ -1,127 +1,70 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "Prompt",
"beta": 0,
"creation": "2019-04-19 15:04:05.317138",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"weight",
"description"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "weight",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Weight",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Weight"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Description"
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-19 15:31:48.080164",
"links": [],
"modified": "2022-08-29 17:46:41.342979",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task Type",
"name_case": "",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Projects Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Projects User",
"share": 1
}
],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"states": [],
"track_changes": 1
}

View File

@@ -39,6 +39,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this._calculate_taxes_and_totals();
this.calculate_discount_amount();
// # Update grand total as per cash and non trade discount
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
this.frm.doc.grand_total -= this.frm.doc.discount_amount;
this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount;
}
await this.calculate_shipping_charges();
// Advance calculation applicable to Sales /Purchase Invoice
@@ -633,6 +639,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.base_discount_amount = flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate,
precision("base_discount_amount"));
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
return;
}
var total_for_discount_amount = this.get_total_for_discount_amount();
var net_total = 0;
// calculate item amount after Discount Amount

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide('erpnext.accounts.dimensions');
erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
setup() {
@@ -794,24 +793,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
set_party_account(set_pricing);
});
// Get default company billing address in Purchase Invoice, Order and Receipt
if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) {
frappe.call({
method: "erpnext.setup.doctype.company.company.get_default_company_address",
args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""},
debounce: 2000,
callback: function(r) {
if (r.message) {
me.frm.set_value("billing_address", r.message);
} else {
if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) {
me.frm.set_value("company_address", "");
}
}
}
});
}
} else {
set_party_account(set_pricing);
}

View File

@@ -3,25 +3,14 @@
frappe.provide("erpnext.utils");
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
erpnext.utils.get_party_details = function(frm, method, args, callback) {
if (!method) {
method = "erpnext.accounts.party.get_party_details";
}
if (args) {
if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
if (frm.doc.company_address && (!args.company_address)) {
args.company_address = frm.doc.company_address;
}
}
if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
if (frm.doc.shipping_address && (!args.shipping_address)) {
args.shipping_address = frm.doc.shipping_address;
}
}
}
if (!args) {
if ((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
@@ -45,41 +34,44 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
};
}
if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
if (!args) {
if (!args) {
if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.customer || frm.doc.party_name,
party_type: 'Customer'
}
}
if (frm.doc.company_address && (!args.company_address)) {
args.company_address = frm.doc.company_address;
};
}
if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) {
args.shipping_address_name = frm.doc.shipping_address_name;
}
}
if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
if (!args) {
if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.supplier,
party_type: 'Supplier'
}
}
if (frm.doc.shipping_address && (!args.shipping_address)) {
args.shipping_address = frm.doc.shipping_address;
};
}
}
if (args) {
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
if (!args || !args.party) return;
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
}
if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
if (!args.company_address && frm.doc.company_address) {
args.company_address = frm.doc.company_address;
}
}
if (!args || !args.party) return;
if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
if (!args.company_address && frm.doc.billing_address) {
args.company_address = frm.doc.billing_address;
}
if (!args.shipping_address && frm.doc.shipping_address) {
args.shipping_address = frm.doc.shipping_address;
}
}
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",

View File

@@ -268,7 +268,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
def set_expired_status():
# filter out submitted non expired quotations whose validity has been ended
cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s"
cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status NOT IN ('Expired', 'Lost') and `tabQuotation`.valid_till < %s"
# check if those QUO have SO against it
so_against_quo = """
SELECT

View File

@@ -892,6 +892,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
target.additional_discount_percentage = 0.0
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
if default_price_list:
@@ -1010,6 +1011,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
target.additional_discount_percentage = 0.0
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
target.customer = ""
target.customer_name = ""
target.run_method("set_missing_values")

View File

@@ -85,7 +85,6 @@
"depreciation_expense_account",
"series_for_depreciation_entry",
"expenses_included_in_asset_valuation",
"repair_and_maintenance_account",
"column_break_40",
"disposal_account",
"depreciation_cost_center",
@@ -234,7 +233,6 @@
"label": "Default Warehouse for Sales Return",
"options": "Warehouse"
},
{
"fieldname": "country",
"fieldtype": "Link",
@@ -678,12 +676,6 @@
"fieldtype": "Section Break",
"label": "Fixed Asset Defaults"
},
{
"fieldname": "repair_and_maintenance_account",
"fieldtype": "Link",
"label": "Repair and Maintenance Account",
"options": "Account"
},
{
"fieldname": "section_break_28",
"fieldtype": "Section Break",
@@ -709,7 +701,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2022-06-30 18:03:18.701314",
"modified": "2022-08-16 16:09:02.327724",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -142,10 +142,6 @@ def get_item_for_list_in_html(context):
if (context.get("website_image") or "").startswith("files/"):
context["website_image"] = "/" + quote(context["website_image"])
context["show_availability_status"] = cint(
frappe.db.get_single_value("E Commerce Settings", "show_availability_status")
)
products_template = "templates/includes/products_as_list.html"
return frappe.get_template(products_template).render(context)

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.utils import cint
@@ -83,35 +83,32 @@ def setup_currency_exchange():
def create_print_setting_custom_fields():
create_custom_field(
"Print Settings",
create_custom_fields(
{
"label": _("Compact Item Print"),
"fieldname": "compact_item_print",
"fieldtype": "Check",
"default": 1,
"insert_after": "with_letterhead",
},
)
create_custom_field(
"Print Settings",
{
"label": _("Print UOM after Quantity"),
"fieldname": "print_uom_after_quantity",
"fieldtype": "Check",
"default": 0,
"insert_after": "compact_item_print",
},
)
create_custom_field(
"Print Settings",
{
"label": _("Print taxes with zero amount"),
"fieldname": "print_taxes_with_zero_amount",
"fieldtype": "Check",
"default": 0,
"insert_after": "allow_print_for_cancelled",
},
"Print Settings": [
{
"label": _("Compact Item Print"),
"fieldname": "compact_item_print",
"fieldtype": "Check",
"default": "1",
"insert_after": "with_letterhead",
},
{
"label": _("Print UOM after Quantity"),
"fieldname": "print_uom_after_quantity",
"fieldtype": "Check",
"default": "0",
"insert_after": "compact_item_print",
},
{
"label": _("Print taxes with zero amount"),
"fieldname": "print_taxes_with_zero_amount",
"fieldtype": "Check",
"default": "0",
"insert_after": "allow_print_for_cancelled",
},
]
}
)

View File

@@ -473,7 +473,13 @@ def make_new_batch(**args):
"doctype": "Batch",
"batch_id": args.batch_id,
"item": args.item_code,
"expiry_date": args.expiry_date,
}
).insert()
)
if args.expiry_date:
batch.expiry_date = args.expiry_date
batch.insert()
return batch

View File

@@ -35,14 +35,39 @@ frappe.ui.form.on('Inventory Dimension', {
refresh(frm) {
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
&& frm.doc.__onload.has_stock_ledger.length) {
let msg = __('Stock transactions exists against this dimension, user can not update document.');
frm.dashboard.add_comment(msg, 'blue', true);
let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
'type_of_transaction', 'condition'];
frm.fields.forEach((field) => {
if (field.df.fieldname !== 'disabled') {
if (!in_list(allow_to_edit_fields, field.df.fieldname)) {
frm.set_df_property(field.df.fieldname, "read_only", "1");
}
});
}
if (!frm.is_new()) {
frm.add_custom_button(__('Delete Dimension'), () => {
frm.trigger('delete_dimension');
});
}
},
delete_dimension(frm) {
let msg = (`
Custom fields related to this dimension will be deleted on deletion of dimension.
<br> Do you want to delete {0} dimension?
`);
frappe.confirm(__(msg, [frm.doc.name.bold()]), () => {
frappe.call({
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension',
args: {
dimension: frm.doc.name
},
callback: function() {
frappe.set_route('List', 'Inventory Dimension');
}
});
});
}
});

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:dimension_name",
"creation": "2022-06-17 13:04:16.554051",
"doctype": "DocType",
@@ -22,6 +21,7 @@
"document_type",
"istable",
"type_of_transaction",
"fetch_from_parent",
"column_break_16",
"condition",
"applicable_condition_example_section",
@@ -101,12 +101,14 @@
"fieldname": "target_fieldname",
"fieldtype": "Data",
"label": "Target Fieldname (Stock Ledger Entry)",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "source_fieldname",
"fieldtype": "Data",
"label": "Source Fieldname",
"no_copy": 1,
"read_only": 1
},
{
@@ -123,7 +125,7 @@
"fieldname": "type_of_transaction",
"fieldtype": "Select",
"label": "Type of Transaction",
"options": "\nInward\nOutward"
"options": "\nInward\nOutward\nBoth"
},
{
"fieldname": "html_19",
@@ -140,11 +142,18 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"depends_on": "istable",
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
"fieldname": "fetch_from_parent",
"fieldtype": "Data",
"label": "Fetch Value From Parent Form"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-07-19 21:06:11.824976",
"modified": "2022-08-17 11:43:24.722441",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",

View File

@@ -43,13 +43,37 @@ class InventoryDimension(Document):
return
old_doc = self._doc_before_save
allow_to_edit_fields = [
"disabled",
"fetch_from_parent",
"type_of_transaction",
"condition",
]
for field in frappe.get_meta("Inventory Dimension").fields:
if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname):
if field.fieldname not in allow_to_edit_fields and old_doc.get(field.fieldname) != self.get(
field.fieldname
):
msg = f"""The user can not change value of the field {bold(field.label)} because
stock transactions exists against the dimension {bold(self.name)}."""
frappe.throw(_(msg), DoNotChangeError)
def on_trash(self):
self.delete_custom_fields()
def delete_custom_fields(self):
filters = {"fieldname": self.source_fieldname}
if self.document_type:
filters["dt"] = self.document_type
for field in frappe.get_all("Custom Field", filters=filters):
frappe.delete_doc("Custom Field", field.name)
msg = f"Deleted custom fields related to the dimension {self.name}"
frappe.msgprint(_(msg))
def reset_value(self):
if self.apply_to_all_doctypes:
self.istable = 0
@@ -76,30 +100,35 @@ class InventoryDimension(Document):
self.add_custom_fields()
def add_custom_fields(self):
dimension_field = dict(
fieldname=self.source_fieldname,
fieldtype="Link",
insert_after="warehouse",
options=self.reference_document,
label=self.dimension_name,
)
dimension_fields = [
dict(
fieldname="inventory_dimension",
fieldtype="Section Break",
insert_after="warehouse",
label="Inventory Dimension",
collapsible=1,
),
dict(
fieldname=self.source_fieldname,
fieldtype="Link",
insert_after="inventory_dimension",
options=self.reference_document,
label=self.dimension_name,
),
]
custom_fields = {}
if self.apply_to_all_doctypes:
for doctype in get_inventory_documents():
if not frappe.db.get_value(
"Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname}
):
custom_fields.setdefault(doctype[0], dimension_field)
elif not frappe.db.get_value(
"Custom Field", {"dt": self.document_type, "fieldname": self.source_fieldname}
):
custom_fields.setdefault(self.document_type, dimension_field)
custom_fields.setdefault(doctype[0], dimension_fields)
else:
custom_fields.setdefault(self.document_type, dimension_fields)
if not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
):
dimension_field = dimension_fields[1]
dimension_field["fieldname"] = self.target_fieldname
custom_fields["Stock Ledger Entry"] = dimension_field
@@ -143,7 +172,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
elif (
row.type_of_transaction == "Outward"
if doc.docstatus == 1
else row.type_of_transaction != "Inward"
else row.type_of_transaction != "Outward"
) and sl_dict.actual_qty > 0:
continue
@@ -166,7 +195,14 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
if not frappe.local.document_wise_inventory_dimensions.get(doctype):
dimensions = frappe.get_all(
"Inventory Dimension",
fields=["name", "source_fieldname", "condition", "target_fieldname", "type_of_transaction"],
fields=[
"name",
"source_fieldname",
"condition",
"target_fieldname",
"type_of_transaction",
"fetch_from_parent",
],
filters={"disabled": 0},
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
)
@@ -194,3 +230,9 @@ def get_inventory_dimensions():
frappe.local.inventory_dimensions = dimensions
return frappe.local.inventory_dimensions
@frappe.whitelist()
def delete_dimension(dimension):
doc = frappe.get_doc("Inventory Dimension", dimension)
doc.delete()

View File

@@ -8,6 +8,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
CanNotBeChildDoc,
CanNotBeDefaultDimension,
DoNotChangeError,
delete_dimension,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -42,6 +43,32 @@ class TestInventoryDimension(FrappeTestCase):
self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert)
def test_delete_inventory_dimension(self):
inv_dim1 = create_inventory_dimension(
reference_document="Shelf",
type_of_transaction="Outward",
dimension_name="From Shelf",
apply_to_all_doctypes=0,
document_type="Stock Entry Detail",
condition="parent.purpose == 'Material Issue'",
)
inv_dim1.save()
custom_field = frappe.db.get_value(
"Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
)
self.assertTrue(custom_field)
delete_dimension(inv_dim1.name)
custom_field = frappe.db.get_value(
"Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
)
self.assertFalse(custom_field)
def test_inventory_dimension(self):
warehouse = "Shelf Warehouse - _TC"
item_code = "_Test Item"

View File

@@ -5,7 +5,7 @@ def get_data():
return {
"heatmap": True,
"heatmap_message": _("This is based on stock movement. See {0} for details").format(
'<a href="#query-report/Stock Ledger">' + _("Stock Ledger") + "</a>"
'<a href="/app/query-report/Stock Ledger">' + _("Stock Ledger") + "</a>"
),
"fieldname": "item_code",
"non_standard_fieldnames": {

View File

@@ -37,7 +37,8 @@
"tc_name",
"terms",
"reference",
"job_card"
"job_card",
"work_order"
],
"fields": [
{
@@ -309,16 +310,24 @@
"label": "Transfer Status",
"options": "\nNot Started\nIn Transit\nCompleted",
"read_only": 1
},
{
"fieldname": "work_order",
"fieldtype": "Link",
"label": "Work Order",
"options": "Work Order",
"read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 70,
"is_submittable": 1,
"links": [],
"modified": "2021-08-17 20:16:12.737743",
"modified": "2022-08-25 11:49:28.155048",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -386,5 +395,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}

View File

@@ -792,10 +792,8 @@
{
"fieldname": "expense_account",
"fieldtype": "Link",
"hidden": 1,
"label": "Expense Account",
"options": "Account",
"read_only": 1
"options": "Account"
},
{
"fieldname": "accounting_dimensions_section",
@@ -1001,7 +999,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-06-17 05:32:16.483178",
"modified": "2022-07-28 19:27:54.880781",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -15,7 +15,7 @@ frappe.ui.form.on('Repost Item Valuation', {
return {
filters: {
name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note',
'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']]
'Sales Invoice', 'Stock Entry', 'Stock Reconciliation', 'Subcontracting Receipt']]
}
};
});
@@ -24,7 +24,8 @@ frappe.ui.form.on('Repost Item Valuation', {
frm.set_query("voucher_no", () => {
return {
filters: {
company: frm.doc.company
company: frm.doc.company,
docstatus: 1
}
};
});

View File

@@ -174,6 +174,8 @@ frappe.ui.form.on('Stock Entry', {
if(!items.length) {
items = frm.doc.items;
}
mr.work_order = frm.doc.work_order;
items.forEach(function(item) {
var mr_item = frappe.model.add_child(mr, 'items');
mr_item.item_code = item.item_code;
@@ -583,18 +585,23 @@ frappe.ui.form.on('Stock Entry', {
},
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('to_warehouse', '');
if(frm.doc.purpose=='Material Transfer') {
var filters = {
'is_group': 0,
'company': frm.doc.company
}
if(frm.doc.add_to_transit){
filters['warehouse_type'] = 'Transit';
frm.set_value('to_warehouse', '');
frm.trigger('set_transit_warehouse');
}
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
filters:filters
};
};
frm.trigger('set_transit_warehouse');
}
},

View File

@@ -116,6 +116,7 @@ class StockEntry(StockController):
self.validate_warehouse()
self.validate_work_order()
self.validate_bom()
self.validate_purchase_order()
if self.purpose in ("Manufacture", "Repack"):
self.mark_finished_and_scrap_items()
@@ -946,6 +947,19 @@ class StockEntry(StockController):
item_code = d.original_item or d.item_code
validate_bom_no(item_code, d.bom_no)
def validate_purchase_order(self):
if self.purpose == "Send to Subcontractor" and self.get("purchase_order"):
is_old_subcontracting_flow = frappe.db.get_value(
"Purchase Order", self.purchase_order, "is_old_subcontracting_flow"
)
if not is_old_subcontracting_flow:
frappe.throw(
_("Please select Subcontracting Order instead of Purchase Order {0}").format(
self.purchase_order
)
)
def mark_finished_and_scrap_items(self):
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
return
@@ -2215,7 +2229,7 @@ class StockEntry(StockController):
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
def update_subcontracting_order_status(self):
if self.subcontracting_order and self.purpose == "Send to Subcontractor":
if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]:
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
update_subcontracting_order_status,
)
@@ -2554,27 +2568,26 @@ def get_supplied_items(
@frappe.whitelist()
def get_items_from_subcontracting_order(source_name, target_doc=None):
sco = frappe.get_doc("Subcontracting Order", source_name)
def post_process(source, target):
target.stock_entry_type = target.purpose = "Send to Subcontractor"
target.subcontracting_order = source_name
if sco.docstatus == 1:
if target_doc and isinstance(target_doc, str):
target_doc = frappe.get_doc(json.loads(target_doc))
if target_doc.items:
target_doc.items = []
if target.items:
target.items = []
warehouses = {}
for item in sco.items:
for item in source.items:
warehouses[item.name] = item.warehouse
for item in sco.supplied_items:
target_doc.append(
for item in source.supplied_items:
target.append(
"items",
{
"s_warehouse": warehouses.get(item.reference_name),
"t_warehouse": sco.supplier_warehouse,
"t_warehouse": source.supplier_warehouse,
"subcontracted_item": item.main_item_code,
"item_code": item.rm_item_code,
"qty": item.required_qty,
"qty": max(item.required_qty - item.total_supplied_qty, 0),
"transfer_qty": item.required_qty,
"uom": item.stock_uom,
"stock_uom": item.stock_uom,
@@ -2582,6 +2595,23 @@ def get_items_from_subcontracting_order(source_name, target_doc=None):
},
)
target_doc = get_mapped_doc(
"Subcontracting Order",
source_name,
{
"Subcontracting Order": {
"doctype": "Stock Entry",
"field_no_map": ["purchase_order"],
"validation": {
"docstatus": ["=", 1],
},
},
},
target_doc,
post_process,
ignore_child_tables=True,
)
return target_doc

View File

@@ -5,7 +5,7 @@
import frappe
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowdate, nowtime
from frappe.utils import add_days, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -1589,6 +1589,31 @@ class TestStockEntry(FrappeTestCase):
self.assertEqual(obj.items[index].basic_rate, 200)
self.assertEqual(obj.items[index].basic_amount, 2000)
def test_batch_expiry(self):
from erpnext.controllers.stock_controller import BatchExpiredError
from erpnext.stock.doctype.batch.test_batch import make_new_batch
item_code = "Test Batch Expiry Test Item - 001"
item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
item_doc.has_batch_no = 1
item_doc.save()
batch = make_new_batch(
batch_id=frappe.generate_hash("", 5), item_code=item_doc.name, expiry_date=add_days(today(), -1)
)
se = make_stock_entry(
item_code=item_code,
purpose="Material Receipt",
qty=4,
to_warehouse="_Test Warehouse - _TC",
batch_no=batch.name,
do_not_save=True,
)
self.assertRaises(BatchExpiredError, se.save)
def make_serialized_item(**args):
args = frappe._dict(args)

View File

@@ -1,4 +1,4 @@
let document_list = ['Landed Cost Voucher', 'Stock Entry'];
let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order', 'Subcontracting Receipt'];
document_list.forEach((doctype) => {
frappe.ui.form.on(doctype, {

View File

@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying');
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.ui.form.on('Subcontracting Order', {
setup: (frm) => {
frm.get_field("items").grid.cannot_add_rows = true;
@@ -105,9 +107,9 @@ frappe.ui.form.on('Subcontracting Order', {
get_materials_from_supplier: function (frm) {
let sco_rm_details = [];
if (frm.doc.supplied_items && (frm.doc.per_received == 100)) {
if (frm.doc.supplied_items && frm.doc.per_received > 0) {
frm.doc.supplied_items.forEach(d => {
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
sco_rm_details.push(d.name);
}
});
@@ -136,6 +138,16 @@ frappe.ui.form.on('Subcontracting Order', {
}
});
frappe.ui.form.on('Landed Cost Taxes and Charges', {
amount: function (frm, cdt, cdn) {
frm.events.set_base_amount(frm, cdt, cdn);
},
expense_account: function (frm, cdt, cdn) {
frm.events.set_account_currency(frm, cdt, cdn);
}
});
erpnext.buying.SubcontractingOrderController = class SubcontractingOrderController {
setup() {
this.frm.custom_make_buttons = {
@@ -148,14 +160,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
var me = this;
if (doc.docstatus == 1) {
if (doc.status != 'Completed') {
if (!['Closed', 'Completed'].includes(doc.status)) {
if (flt(doc.per_received) < 100) {
cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create'));
if (me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'),
() => {
me.make_stock_entry();
}, __('Transfer'));
cur_frm.add_custom_button(__('Material to Supplier'), this.make_stock_entry, __('Transfer'));
}
}
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
@@ -183,120 +192,6 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
}
make_stock_entry() {
var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false);
var me = this;
if (items.length >= 1) {
me.raw_material_data = [];
me.show_dialog = 1;
let title = __('Transfer Material to Supplier');
let fields = [
{ fieldtype: 'Section Break', label: __('Raw Materials') },
{
fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'),
fields: [
{
fieldtype: 'Data',
fieldname: 'item_code',
label: __('Item'),
read_only: 1,
in_list_view: 1
},
{
fieldtype: 'Data',
fieldname: 'rm_item_code',
label: __('Raw Material'),
read_only: 1,
in_list_view: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'qty',
label: __('Quantity'),
in_list_view: 1
},
{
fieldtype: 'Data',
read_only: 1,
fieldname: 'warehouse',
label: __('Reserve Warehouse'),
in_list_view: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'rate',
label: __('Rate'),
hidden: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'amount',
label: __('Amount'),
hidden: 1
},
{
fieldtype: 'Link',
read_only: 1,
fieldname: 'uom',
label: __('UOM'),
hidden: 1
}
],
data: me.raw_material_data,
get_data: () => me.raw_material_data
}
];
me.dialog = new frappe.ui.Dialog({
title: title, fields: fields
});
if (me.frm.doc['supplied_items']) {
me.frm.doc['supplied_items'].forEach((item) => {
if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
me.raw_material_data.push({
'name': item.name,
'item_code': item.main_item_code,
'rm_item_code': item.rm_item_code,
'item_name': item.rm_item_code,
'qty': item.required_qty - item.supplied_qty,
'warehouse': item.reserve_warehouse,
'rate': item.rate,
'amount': item.amount,
'stock_uom': item.stock_uom
});
me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
}
});
}
me.dialog.get_field('sub_con_rm_items').check_all_rows();
me.dialog.show();
this.dialog.set_primary_action(__('Transfer'), () => {
me.values = me.dialog.get_values();
if (me.values) {
me.values.sub_con_rm_items.map((row, i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
let row_id = i + 1;
frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id]));
}
});
me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children());
me.dialog.hide();
}
});
}
me.dialog.get_close_btn().on('click', () => {
me.dialog.hide();
});
}
has_unsupplied_items() {
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
}
@@ -309,6 +204,15 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
});
}
make_stock_entry() {
frappe.model.open_mapped_doc({
method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order',
source_name: cur_frm.doc.name,
freeze: true,
freeze_message: __('Creating Stock Entry ...')
});
}
make_rm_stock_entry(rm_items) {
frappe.call({
method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry',

View File

@@ -19,6 +19,10 @@
"transaction_date",
"schedule_date",
"amended_from",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"address_and_contact_section",
"supplier_address",
"address_display",
@@ -422,12 +426,34 @@
"fieldtype": "Select",
"label": "Distribute Additional Costs Based On ",
"options": "Qty\nAmount"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2022-04-11 21:02:44.097841",
"modified": "2022-08-15 14:08:49.204218",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order",

View File

@@ -82,25 +82,6 @@ class SubcontractingOrder(SubcontractingController):
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
def set_missing_values_in_additional_costs(self):
if self.get("additional_costs"):
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
if self.total_additional_costs:
if self.distribute_additional_costs_based_on == "Amount":
total_amt = sum(flt(item.amount) for item in self.get("items"))
for item in self.items:
item.additional_cost_per_qty = (
(item.amount * self.total_additional_costs) / total_amt
) / item.qty
else:
total_qty = sum(flt(item.qty) for item in self.get("items"))
additional_cost_per_qty = self.total_additional_costs / total_qty
for item in self.items:
item.additional_cost_per_qty = additional_cost_per_qty
else:
self.total_additional_costs = 0
def set_missing_values_in_service_items(self):
for idx, item in enumerate(self.get("service_items")):
self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty
@@ -114,9 +95,7 @@ class SubcontractingOrder(SubcontractingController):
def set_missing_values_in_items(self):
total_qty = total = 0
for item in self.items:
item.rate = (
item.rm_cost_per_qty + item.service_cost_per_qty + (item.additional_cost_per_qty or 0)
)
item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty)
item.amount = item.qty * item.rate
total_qty += flt(item.qty)
total += flt(item.amount)
@@ -174,7 +153,7 @@ class SubcontractingOrder(SubcontractingController):
else:
self.set_missing_values()
def update_status(self, status=None, update_modified=False):
def update_status(self, status=None, update_modified=True):
if self.docstatus >= 1 and not status:
if self.docstatus == 1:
if self.status == "Draft":
@@ -183,11 +162,15 @@ class SubcontractingOrder(SubcontractingController):
status = "Completed"
elif self.per_received > 0 and self.per_received < 100:
status = "Partially Received"
for item in self.supplied_items:
if item.returned_qty:
status = "Closed"
break
else:
total_required_qty = total_supplied_qty = 0
for item in self.supplied_items:
total_required_qty += item.required_qty
total_supplied_qty += item.supplied_qty or 0
total_supplied_qty += flt(item.supplied_qty)
if total_supplied_qty:
status = "Partial Material Transferred"
if total_supplied_qty >= total_required_qty:
@@ -197,7 +180,10 @@ class SubcontractingOrder(SubcontractingController):
elif self.docstatus == 2:
status = "Cancelled"
frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified)
if status:
frappe.db.set_value(
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
)
@frappe.whitelist()

View File

@@ -10,6 +10,7 @@ frappe.listview_settings['Subcontracting Order'] = {
"Completed": "green",
"Partial Material Transferred": "purple",
"Material Transferred": "blue",
"Closed": "red",
};
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
},

View File

@@ -7,7 +7,10 @@ import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.controllers.subcontracting_controller import (
get_materials_from_supplier,
make_rm_stock_entry,
)
from erpnext.controllers.tests.test_subcontracting_controller import (
get_rm_items,
get_subcontracting_order,
@@ -89,6 +92,16 @@ class TestSubcontractingOrder(FrappeTestCase):
sco.load_from_db()
self.assertEqual(sco.status, "Partially Received")
# Closed
ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
ste.save()
ste.submit()
sco.load_from_db()
self.assertEqual(sco.status, "Closed")
ste.cancel()
sco.load_from_db()
self.assertEqual(sco.status, "Partially Received")
# Completed
scr = make_subcontracting_receipt(sco.name)
scr.save()

View File

@@ -40,6 +40,10 @@
"manufacture_section",
"manufacturer",
"manufacturer_part_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_34",
"page_break"
],
@@ -304,13 +308,35 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-04-11 21:28:06.585338",
"modified": "2022-08-15 14:25:45.177703",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",

View File

@@ -150,8 +150,7 @@
"label": "Returned Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"hidden": 1
"read_only": 1
},
{
"fieldname": "total_supplied_qty",
@@ -166,7 +165,7 @@
"hide_toolbar": 1,
"istable": 1,
"links": [],
"modified": "2022-04-07 12:58:28.208847",
"modified": "2022-08-26 16:04:56.125951",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Supplied Item",

View File

@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying');
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.ui.form.on('Subcontracting Receipt', {
setup: (frm) => {
frm.get_field('supplied_items').grid.cannot_add_rows = true;
@@ -48,6 +50,13 @@ frappe.ui.form.on('Subcontracting Receipt', {
is_group: 0
}
}));
frm.set_query("expense_account", "items", function () {
return {
query: "erpnext.controllers.queries.get_expense_account",
filters: { 'company': frm.doc.company }
};
});
},
refresh: (frm) => {
@@ -121,6 +130,16 @@ frappe.ui.form.on('Subcontracting Receipt', {
},
});
frappe.ui.form.on('Landed Cost Taxes and Charges', {
amount: function (frm, cdt, cdn) {
frm.events.set_base_amount(frm, cdt, cdn);
},
expense_account: function (frm, cdt, cdn) {
frm.events.set_account_currency(frm, cdt, cdn);
}
});
frappe.ui.form.on('Subcontracting Receipt Item', {
item_code(frm) {
set_missing_values(frm);

View File

@@ -15,8 +15,13 @@
"company",
"posting_date",
"posting_time",
"set_posting_time",
"is_return",
"return_against",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_addresses",
"supplier_address",
"contact_person",
@@ -43,12 +48,14 @@
"raw_material_details",
"get_current_stock",
"supplied_items",
"additional_costs_section",
"distribute_additional_costs_based_on",
"additional_costs",
"total_additional_costs",
"section_break_46",
"in_words",
"bill_no",
"bill_date",
"accounting_details_section",
"provisional_expense_account",
"more_info",
"status",
"column_break_39",
@@ -132,6 +139,7 @@
"label": "Date",
"no_copy": 1,
"print_width": "100px",
"read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1,
"search_index": 1,
"width": "100px"
@@ -144,6 +152,7 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
"read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1,
"width": "100px"
},
@@ -360,7 +369,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled",
"options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -520,19 +529,6 @@
"options": "Company",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "provisional_expense_account",
"fieldtype": "Link",
"hidden": 1,
"label": "Provisional Expense Account",
"options": "Account"
},
{
"default": "0",
"fieldname": "is_return",
@@ -569,11 +565,70 @@
{
"fieldname": "section_break_47",
"fieldtype": "Section Break"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions "
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"collapsible": 1,
"collapsible_depends_on": "total_additional_costs",
"depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
"fieldname": "additional_costs_section",
"fieldtype": "Section Break",
"label": "Additional Costs"
},
{
"default": "Qty",
"fieldname": "distribute_additional_costs_based_on",
"fieldtype": "Select",
"label": "Distribute Additional Costs Based On ",
"options": "Qty\nAmount"
},
{
"fieldname": "additional_costs",
"fieldtype": "Table",
"label": "Additional Costs",
"options": "Landed Cost Taxes and Charges"
},
{
"fieldname": "total_additional_costs",
"fieldtype": "Currency",
"label": "Total Additional Costs",
"print_hide_if_no_value": 1,
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==0",
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
"print_hide": 1
}
],
"in_create": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-04-18 13:15:12.011682",
"modified": "2022-08-26 21:02:26.353870",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _
from frappe.utils import cint, getdate, nowdate
from frappe.utils import cint, flt, getdate, nowdate
from erpnext.controllers.subcontracting_controller import SubcontractingController
@@ -103,6 +103,7 @@ class SubcontractingReceipt(SubcontractingController):
@frappe.whitelist()
def set_missing_values(self):
self.set_missing_values_in_additional_costs()
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
@@ -125,12 +126,12 @@ class SubcontractingReceipt(SubcontractingController):
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
rm_supp_cost.pop(item.name)
if self.is_new() and item.rm_supp_cost > 0:
if item.recalculate_rate:
item.rate = (
item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
)
item.received_qty = item.qty + (item.rejected_qty or 0)
item.received_qty = item.qty + flt(item.rejected_qty)
item.amount = item.qty * item.rate
total_qty += item.qty
total_amount += item.amount

View File

@@ -29,6 +29,7 @@
"rate_and_amount",
"rate",
"amount",
"recalculate_rate",
"column_break_19",
"rm_cost_per_qty",
"service_cost_per_qty",
@@ -49,15 +50,16 @@
"col_break5",
"batch_no",
"rejected_serial_no",
"expense_account",
"manufacture_details",
"manufacturer",
"column_break_16",
"manufacturer_part_no",
"accounting_details_section",
"expense_account",
"accounting_dimensions_section",
"project",
"dimension_col_break",
"cost_center",
"dimension_col_break",
"project",
"section_break_80",
"page_break"
],
@@ -192,6 +194,8 @@
"label": "Rate",
"options": "currency",
"print_width": "100px",
"read_only": 1,
"read_only_depends_on": "eval: doc.recalculate_rate",
"width": "100px"
},
{
@@ -363,10 +367,8 @@
{
"fieldname": "expense_account",
"fieldtype": "Link",
"hidden": 1,
"label": "Expense Account",
"options": "Account",
"read_only": 1
"options": "Account"
},
{
"collapsible": 1,
@@ -456,12 +458,23 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"default": "1",
"fieldname": "recalculate_rate",
"fieldtype": "Check",
"label": "Recalculate Rate"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-04-21 12:07:55.899701",
"modified": "2022-08-20 17:16:48.269164",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",

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