Compare commits

..

1095 Commits

Author SHA1 Message Date
Deepesh Garg
c2dec3389b Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-04-13 14:11:23 +05:30
Deepesh Garg
512b480f6a chore: version bump 2022-04-12 23:31:47 +05:30
Deepesh Garg
d5d6b34380 Merge pull request #30695 from deepeshgarg007/v13_26_0
chore: change log and version bump
2022-04-12 23:29:44 +05:30
Deepesh Garg
2c2d5ccb8f Merge pull request #30694 from frappe/version-13-pre-release
chore: Release for v13.26.0
2022-04-12 23:28:24 +05:30
Deepesh Garg
147499bc9c chore: change log and version bump 2022-04-12 23:01:09 +05:30
Deepesh Garg
08e2b9110c Merge pull request #30693 from frappe/version-13-hotfix
chore: Pre release for v13.26.0
2022-04-12 16:56:52 +05:30
Deepesh Garg
4a48a6aae3 Merge pull request #30690 from frappe/mergify/bp/version-13-hotfix/pr-30686
feat: Ignore permlevel for specific fields (backport #30686)
2022-04-12 16:10:51 +05:30
Nabin Hait
e650d99cdd feat: Ignore permlevel for specific fields
(cherry picked from commit 993c6c0de9)
2022-04-12 10:26:17 +00:00
Deepesh Garg
23ccadbcae Merge pull request #30683 from frappe/mergify/bp/version-13-hotfix/pr-30651
fix: Download JSON for GSTR-1 report (backport #30651)
2022-04-12 14:00:55 +05:30
Deepesh Garg
bfd8ab6d72 Merge pull request #30687 from frappe/mergify/bp/version-13-hotfix/pr-30626
feat(india): e-invoicing for intra-state union territory transactions (backport #30626)
2022-04-12 14:00:38 +05:30
Saqib Ansari
2ab431ad36 feat(india): e-invoicing for intra-state union territory transactions
(cherry picked from commit 45fca6bed7)
2022-04-12 08:03:23 +00:00
mergify[bot]
21066a48b6 fix: ignore item-less maintenance visit for sr no (#30684) (#30685)
(cherry picked from commit 60fb71bd2a)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-12 11:16:59 +05:30
Deepesh Garg
9efc0d1043 fix: Download JSON for GSTR-1 report
(cherry picked from commit b532ade383)
2022-04-11 18:37:17 +00:00
Deepesh Garg
54ea9319a1 Merge pull request #30680 from frappe/mergify/bp/version-13-hotfix/pr-30602
fix: Deferred Revenue/Expense Account validation (backport #30602)
2022-04-11 20:54:52 +05:30
Marica
f54f9771e4 Merge pull request #30681 from frappe/mergify/bp/version-13-hotfix/pr-30674
fix: Handle multiple item transfer in separate SEs against WO (backport #30674)
2022-04-11 17:46:12 +05:30
marination
e322e7654a test: Multiple RM transfer in separate Stock Entries
- Added test and acceptance of 0 as For Quantity in test helper

(cherry picked from commit 5aa60bb651)
2022-04-11 11:54:48 +00:00
marination
c674180f1a style: Missing Semicolon
(cherry picked from commit be2e5ce966)
2022-04-11 11:54:48 +00:00
marination
d079d8771b fix: Handle multiple item transfer in separate SEs against WO
- Check for pending qty in child items to show/hide "Start" button
- If no qty needed to transfer (FG qty is fulfilled), but RM qty pending: map pending in SE with For Quantity = 0

(cherry picked from commit dfff4beaf4)
2022-04-11 11:54:48 +00:00
Deepesh Garg
4296fc36cc test: Add test
(cherry picked from commit 553178bfe7)
2022-04-11 11:33:33 +00:00
Deepesh Garg
f6c9f052d2 fix: Deferred Revenue/Expense Account validation
(cherry picked from commit 9bf5f76ac8)
2022-04-11 11:33:33 +00:00
Ankush Menat
c9af4e8ce5 chore: broken link 2022-04-11 16:55:22 +05:30
mergify[bot]
e91dea62b8 fix: update translation (#30654) (#30676)
(cherry picked from commit 03c631d723)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2022-04-11 14:48:17 +05:30
Deepesh Garg
cc3c9c7042 Merge pull request #30662 from deepeshgarg007/implicit_ignore_prcing_rule_check_on_returns
fix: Implicit ignore pricing rule check on returns
2022-04-09 20:15:57 +05:30
Deepesh Garg
8e742d6612 Merge pull request #30663 from frappe/mergify/bp/version-13-hotfix/pr-30542
fix: Ignore disabled tax categories (backport #30542)
2022-04-09 20:15:30 +05:30
Deepesh Garg
37dc11e59a fix: Ignore disabled tax categories
(cherry picked from commit 9a6a181f14)
2022-04-09 14:22:53 +00:00
Deepesh Garg
1b25a7fe76 fix: Implicit ignore pricing rule check on returns 2022-04-09 19:20:51 +05:30
mergify[bot]
e69849d333 fix(pos): cannot change paid amount in pos payments (#30661) 2022-04-09 16:28:25 +05:30
Deepesh Garg
71f72458bf chore: version bump 2022-04-09 16:22:22 +05:30
Saqib Ansari
81812b608f fix(pos): cannot change paid amount in pos payments (#30659) 2022-04-09 16:14:26 +05:30
Saqib Ansari
e4bb81d830 fix(pos): cannot change paid amount in pos payments (#30657) 2022-04-09 16:13:29 +05:30
Jannat Patel
d66979ff52 Merge pull request #30650 from frappe/mergify/bp/version-13-hotfix/pr-30596 2022-04-08 16:50:38 +05:30
Jannat Patel
2ccf58d6ad fix: removed unused courses template
(cherry picked from commit bce1c2a028)
2022-04-08 10:58:18 +00:00
Ankush Menat
9b6c5f2e54 test: prevent cancelling RIV of cancelled voucher
(cherry picked from commit d74181630a)
2022-04-08 16:14:01 +05:30
Ankush Menat
8cac70a63c fix: prevent deleting repost queue for cancelled transactions
(cherry picked from commit a281998bcb)
2022-04-08 16:14:01 +05:30
Deepesh Garg
8d4d12c3b0 Merge pull request #30633 from frappe/mergify/bp/version-13-hotfix/pr-30606
fix: Exchange gain and loss button in Payment Entry (backport #30606)
2022-04-08 13:05:23 +05:30
mergify[bot]
341e8dffd3 fix: remove bad defaults from BOM operation (#30644) (#30645)
[skip ci]

(cherry picked from commit 49560d20bc)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-08 11:27:17 +05:30
Deepesh Garg
13f9207b41 Merge pull request #30620 from nabinhait/party-account-in-ar-ap-report-v13
feat: Receivable/Payable Account column and filter in AR/AP report
2022-04-07 21:24:56 +05:30
Deepesh Garg
a4ea9fa801 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30606 2022-04-07 21:24:24 +05:30
mergify[bot]
c0cf18d2d6 Merge pull request #30624 from frappe/mergify/bp/version-13-hotfix/pr-30499
feat: 'customer' column and more filter to Payment terms status report (backport #30499)
2022-04-07 13:47:58 +00:00
Ankush Menat
d62928ed65 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30499 2022-04-07 19:02:47 +05:30
mergify[bot]
4a7ae5c4a7 fix: dont reassign mutable (list) to a different field (backport #30634) (#30635)
This is an automatic backport of pull request #30634 done by [Mergify](https://mergify.com).
Conflicts fixed.
2022-04-07 13:30:24 +00:00
Deepesh Garg
be438c08db fix: Exchange gain and loss button in Payment Entry
(cherry picked from commit 8feb4f08c5)
2022-04-07 11:19:25 +00:00
Nabin Hait
709fcd5051 feat: Receivable/Payable Account column and filter in AR/AP report 2022-04-07 15:56:42 +05:30
mergify[bot]
560c55935f fix: warehouse naming when suffix is present (#30621) (#30629)
(cherry picked from commit be04eaf723)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 15:37:33 +05:30
Chillar Anand
30a86951c3 test: Fix failing tests for healthcare service unit (#30625) 2022-04-07 14:58:38 +05:30
mergify[bot]
4b77a4de28 fix: update translation (backport #30474) (#30593)
* fix: update translation (#30474)

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

(cherry picked from commit 4895761d89)

* chore: fix translation csv file

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 13:24:48 +05:30
Rucha Mahabal
8932d7040f fix: conflicts 2022-04-07 13:21:06 +05:30
Rucha Mahabal
07ff2cb1d3 fix: conflicts 2022-04-07 13:21:06 +05:30
Rucha Mahabal
8393d113d4 fix: show allocation history for earned leaves allocated via scheduler
(cherry picked from commit ec65af5f38)
2022-04-07 13:21:06 +05:30
Rucha Mahabal
c68a38eb5b fix: make New Leaves Allocated read-only if policy assignment is linked to the allocation and leave type is earned leave
(cherry picked from commit 6203ffc8fa)
2022-04-07 13:21:06 +05:30
Rucha Mahabal
0c163eef23 fix: enable Track Changes in Leave Allocation
(cherry picked from commit f8f1c3d8b5)

# Conflicts:
#	erpnext/hr/doctype/leave_allocation/leave_allocation.json
2022-04-07 13:21:06 +05:30
mergify[bot]
af039be03e fix: fallback to item_name if description is not found (backport #30619) (#30622)
* fix: strip html tags before checking for empty description (#30619)

(cherry picked from commit e4c6d6a1a6)

# Conflicts:
#	erpnext/stock/doctype/item/test_item.py

* fix: resolve conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 13:20:36 +05:30
ruthra kumar
39f8ee2ea6 test: added test cases for group filters
(cherry picked from commit 16bfb930f8)
2022-04-07 07:32:04 +00:00
ruthra kumar
a9d5000eab refactor: use group fields from Sales Order and Sales Order Items
(cherry picked from commit b2ed9fd3fe)
2022-04-07 07:32:03 +00:00
ruthra kumar
00acb000dc refactor: item filters are linked with group filters
(cherry picked from commit e324d668d3)
2022-04-07 07:32:02 +00:00
ruthra kumar
9336a3413f refactor: adding new filters and column to test cases
(cherry picked from commit 7558f1b078)
2022-04-07 07:32:02 +00:00
ruthra kumar
c6f946e3e8 feat: additional filters in payment terms status report
(cherry picked from commit eaeadbc422)
2022-04-07 07:32:01 +00:00
Deepesh Garg
da7a2bb3a6 chore: version bump 2022-04-06 22:37:47 +05:30
Saqib Ansari
d720d15e3d fix(pos): reload doc before set value (#30610) 2022-04-06 22:32:23 +05:30
Saqib Ansari
23a7ab15b5 fix(pos): reload doc before set value (#30612) 2022-04-06 22:31:50 +05:30
Saqib Ansari
2e29cf38bb Merge pull request #30611 from nextchamp-saqib/fix-pos-patch-v13
fix(pos): reload doc before set value
2022-04-06 22:28:49 +05:30
Saqib Ansari
f94afc1fff fix(pos): reload doc before set value 2022-04-06 22:14:37 +05:30
Deepesh Garg
78e8f047b5 Merge pull request #30604 from deepeshgarg007/v13_25_0
chore: version bump and change log for v13.25.0
2022-04-06 19:38:41 +05:30
Deepesh Garg
a2d4ae0482 Merge pull request #30605 from frappe/version-13-pre-release
chore: Release for v13.25.0
2022-04-06 19:38:16 +05:30
Deepesh Garg
72d04c66e2 Merge branch 'version-13' into version-13-pre-release 2022-04-06 18:49:58 +05:30
Deepesh Garg
2f7b192ff9 chore: version bump and change log for v13.25.0 2022-04-06 18:46:00 +05:30
Ankush Menat
f89c1b2c0c fix: dont trigger closed WO check on new Job card 2022-04-06 18:28:54 +05:30
mergify[bot]
52aa3561e3 fix: hide pending qty only if original item is assigned (#30599) (#30600)
(cherry picked from commit 8b090a9f7d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 16:38:25 +05:30
mergify[bot]
3c33a5e6ac fix: hide pending qty only if original item is assigned (#30599) (#30601)
(cherry picked from commit 8b090a9f7d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 16:37:15 +05:30
mergify[bot]
14f46a3e26 fix: check null values in is_cancelled patch (#30594) (#30595)
(cherry picked from commit bb875fe217)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 15:14:37 +05:30
Deepesh Garg
3fabc48744 Merge pull request #30592 from frappe/version-13-hotfix
chore: Pre-release for v13.25.0
2022-04-06 15:06:22 +05:30
Marica
50e753330f Merge pull request #30520 from frappe/mergify/bp/version-13-hotfix/pr-30146
refactor: Add exception handling in background job within BOM Update Tool (backport #30146)
2022-04-06 13:50:08 +05:30
Deepesh Garg
ff142f8809 Merge pull request #30589 from frappe/mergify/bp/version-13-hotfix/pr-30557
fix: Issues on loan repayment (backport #30557)
2022-04-06 13:38:39 +05:30
Deepesh Garg
d7af8e1dfe fix: Issues on loan repayment
(cherry picked from commit 194605823e)
2022-04-06 06:07:34 +00:00
Deepesh Garg
08bb18ae8a Merge pull request #30586 from frappe/mergify/bp/version-13-hotfix/pr-30555
fix: Ignore user perm for party account company (backport #30555)
2022-04-06 11:35:56 +05:30
Deepesh Garg
ae11562611 test: Ignore parent company account creation
(cherry picked from commit dec0c1b5bb)
2022-04-06 05:27:32 +00:00
Deepesh Garg
027b7e7de1 fix: Ignore user perm for party account company
(cherry picked from commit 18a3c5d536)
2022-04-06 05:27:32 +00:00
Deepesh Garg
3a55cad05f Merge pull request #30575 from deepeshgarg007/pos_shipping
fix: Do not apply shipping rule for POS transactions
2022-04-06 10:00:21 +05:30
Deepesh Garg
95298f0400 fix: Use get instead of dot 2022-04-06 09:27:40 +05:30
marination
e186d76337 fix: Linter 2022-04-05 19:05:30 +05:30
marination
ec646a1a44 fix: User Literal from typing_extensions as its not supported in typing in py 3.7
- https://mypy.readthedocs.io/en/stable/literal_types.html
2022-04-05 18:45:55 +05:30
Marica
c5c578d7b4 Merge pull request #30582 from frappe/mergify/bp/version-13-hotfix/pr-30578
chore: Accessibility for E-commerce Doctypes (backport #30578)
2022-04-05 18:32:02 +05:30
marination
e8f3e23008 fix: Merge Conflicts 2022-04-05 18:22:35 +05:30
marination
c636b366d5 chore: Add Prices, Stock and E-com Settings access from Website Item
(cherry picked from commit 065623ce25)
2022-04-05 12:42:40 +00:00
marination
732b302be0 chore: Accessibility for E-commerce Doctypes
- Add Website Item routing button and dashboard link in Item master
- Group Item variant buttons together

(cherry picked from commit d4301d6d2f)
2022-04-05 12:42:40 +00:00
Saqib Ansari
e5db8c57f9 Merge pull request #30581 from frappe/mergify/bp/version-13-hotfix/pr-30579
fix: fetch from fields not working in eway bill dialog (backport #30579)
2022-04-05 17:07:34 +05:30
Saqib Ansari
072c3d22b1 Merge pull request #30580 from frappe/mergify/bp/version-13-hotfix/pr-30553
fix(india): minor e-invoicing fixes (backport #30553)
2022-04-05 17:07:22 +05:30
Saqib Ansari
0700ee8a06 fix: fetch from fields not working in eway bill dialog
(cherry picked from commit c8779aa446)
2022-04-05 10:22:59 +00:00
Saqib Ansari
e1bd156f40 fix: server error while viewing gst e-invoice
(cherry picked from commit b91bf40f1b)
2022-04-05 10:22:23 +00:00
Saqib Ansari
01404b2b5b fix(india): cannot generate e-invoice for is_pos invoices
* If mode of payment > 18 characters, the e-invoice portal throws error

(cherry picked from commit 0c26f9a8c8)
2022-04-05 10:22:23 +00:00
Rucha Mahabal
6b1a27a641 Merge pull request #30576 from frappe/mergify/bp/version-13-hotfix/pr-30569 2022-04-04 20:40:49 +05:30
Rucha Mahabal
cb2a8aab31 fix(test): set company for employee in leave allocation test setup
(cherry picked from commit 793164ac2e)
2022-04-04 14:49:20 +00:00
Rucha Mahabal
e41f35aa82 test: leave allocation validations and total value for updates done before and after submission
(cherry picked from commit 5499cecffd)
2022-04-04 14:49:20 +00:00
Rucha Mahabal
1093307dcc fix: total leaves allocated not validated and recalculated on updates post submission
(cherry picked from commit 3538656a7d)
2022-04-04 14:49:19 +00:00
rohitwaghchaure
3f13ce5620 Merge pull request #30570 from frappe/mergify/bp/version-13-hotfix/pr-30564
fix: if accepted warehouse not selected during rejection then stock ledger not created (backport #30564)
2022-04-04 20:15:15 +05:30
Deepesh Garg
c0ebcfb393 fix: Do not apply shipping rule for POS transactions 2022-04-04 20:05:10 +05:30
Marica
761f8e0b1c Merge pull request #30539 from marination/redisearch-app-install-v13
feat: Redisearch with consent (bp)
2022-04-04 19:25:29 +05:30
Sherin KR
0a2c72c594 fix: Validation for single threshold in Tax With Holding Category (#30382) 2022-04-04 19:07:51 +05:30
Rohit Waghchaure
3038a5cd5a test: test case to validate rejected qty without accepted warehouse
(cherry picked from commit ac5df1abbe)
2022-04-04 12:57:04 +00:00
Rohit Waghchaure
157461ed02 fix: if accepted warehouse not selected during rejection then stock ledger not created
(cherry picked from commit 0a71cabab1)
2022-04-04 12:57:04 +00:00
Marica
3e84607cd4 Merge branch 'version-13-hotfix' into redisearch-app-install-v13 2022-04-04 18:01:36 +05:30
mergify[bot]
012cab80bf fix(ux): refresh update to zero val checkbox (#30567) (#30568)
(cherry picked from commit de83511091)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-04 17:31:26 +05:30
mergify[bot]
b524e657e2 fix(pos): do not reset search input on item selection (backport #30537) 2022-04-04 16:35:46 +05:30
Ankush Menat
4cc23830f1 fix: maintain FIFO queue even if outgoing_rate is not found (#30563)
port of #30560
2022-04-04 15:46:49 +05:30
Marica
e5c303e9f4 Merge branch 'version-13-hotfix' into redisearch-app-install-v13 2022-04-04 13:28:09 +05:30
marination
4dc7047a59 chore: Add TODOs(perf) for Redisearch Query 2022-04-04 13:26:08 +05:30
marination
42ec9db5f4 fix: Payload incorrect data (pass item_group.name) 2022-04-04 12:35:14 +05:30
marination
0f9277a8c8 fix: Add default score of 1 to Item Group Autocompleter
- If score 0 is inserted into suggestions, RS does not consider that suggestion
2022-04-04 12:06:27 +05:30
marination
5c36d12369 fix: Convert payload to string before adding to autocompleter 2022-04-04 11:34:39 +05:30
marination
b4d427b429 fix: Better Exception Handling and vaeiabl naming
- Function to handle RS exceptions (create log and raise error)
- Handle `ResponseError` where it is anticipated
- Misc: Better variables
2022-04-04 11:15:45 +05:30
rohitwaghchaure
d032a0cd77 Merge pull request #30450 from anupamvs/crm-contract-naming-v13
feat: configurable Contract naming
2022-04-02 23:43:02 +05:30
Deepesh Garg
00f6565596 Merge pull request #30550 from frappe/mergify/bp/version-13-hotfix/pr-30191
fix: incorrect payable amount for loan closure (backport #30191)
2022-04-02 23:32:56 +05:30
Deepesh Garg
7bf6de1883 fix: Resolve conflicts 2022-04-02 20:33:46 +05:30
Deepesh Garg
3fc43cb259 fix: Code cleanup
(cherry picked from commit 1b2c6a5b78)

# Conflicts:
#	erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
2022-04-02 14:23:50 +00:00
Abhinav Raut
1d9a6efb1b fix: incorrect payable amount for loan closure
(cherry picked from commit 8c76a76154)

# Conflicts:
#	erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
2022-04-02 14:23:50 +00:00
Abhinav Raut
c44a4e559b fix: incorrect payable amount for loan closure
- Add penalty amount to payable amount for loan closure

(cherry picked from commit 4e92926a52)

# Conflicts:
#	erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
2022-04-02 14:23:49 +00:00
marination
17f95b1f83 fix: Use Payload in AutoCompleter (categories in search) and misc
- Separate Item group and Item autocomplete dict definition
- Add payload along with Item group, containing namke and route
- Pass weightage while defining item group autocomplete dict (auto sort)
- Use payload while getting results for categories in search
- Remove check to show categories, always show
- Search fields mandatory if reidsearch enabled
- Code separation (rough)
2022-04-01 18:51:22 +05:30
mergify[bot]
c5fd77ebf7 perf: index barcode for faster scans (#30543) (#30544)
(cherry picked from commit 6c5b01c60d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-01 15:50:04 +05:30
Alberto826
63dacaa03d fix: Remove trailing slashes "/" from route (#30531)
Trailing slashes "/" in the routes causes redirect to port 8080 in docker implementation with reverse proxy.
The "student_admission_row.html" template has an href attribute with a trailing slash.
2022-04-01 14:48:15 +05:30
mergify[bot]
a35cc7004d fix: convert dates to datetime before comparing in leave days calculation and fix half day edge case (backport #30538) (#30541) 2022-04-01 14:17:25 +05:30
marination
fd29722d36 fix: Call Redisearch index creation functions on enabling redisearch in settings 2022-04-01 13:05:17 +05:30
Saqib Ansari
9a5acc313c Merge pull request #30515 from frappe/mergify/copy/version-13-hotfix/pr-30324
fix: multiple pos issues (copy #30324)
2022-04-01 12:11:06 +05:30
Saqib Ansari
9335578a0b fix: linting errors 2022-04-01 11:02:18 +05:30
Deepesh Garg
af6dcd66dc Merge pull request #30534 from frappe/mergify/bp/version-13-hotfix/pr-30510
fix: Taxes getting overriden from mapped to target doc (backport #30510)
2022-04-01 10:31:32 +05:30
Deepesh Garg
6c53ca6328 Merge pull request #30512 from frappe/mergify/bp/version-13-hotfix/pr-30486
fix: Account currency validation (backport #30486)
2022-04-01 09:45:45 +05:30
Deepesh Garg
20df023b9a Merge pull request #30525 from frappe/mergify/bp/version-13-hotfix/pr-30500
fix(India): Tax fetching based on tax category (backport #30500)
2022-04-01 09:45:28 +05:30
Deepesh Garg
328b9431b2 fix: Taxes getting overriden from mapped to target doc
(cherry picked from commit 4720969ce6)
2022-04-01 04:14:07 +00:00
Saqib Ansari
a9f24859ad Merge pull request #30514 from frappe/mergify/bp/version-13-hotfix/pr-30461
fix(asset): do not validate warehouse on asset purchase (backport #30461)
2022-03-31 21:08:51 +05:30
rohitwaghchaure
dbb872aeb4 Merge pull request #30530 from frappe/mergify/bp/version-13-hotfix/pr-30527
feat: minor, pick list item reference on delivery note item table (backport #30527)
2022-03-31 21:04:02 +05:30
Rohit Waghchaure
a74198f974 test: test case to check pick list name has mapped or not
(cherry picked from commit 2f51011f91)
2022-03-31 14:53:52 +00:00
Rohit Waghchaure
c92df4eed3 feat: minor, pick list item reference on delivery note item table
(cherry picked from commit 2f63ae2ee9)
2022-03-31 14:53:51 +00:00
Deepesh Garg
f213dc9999 fix(India): Tax fetching based on tax category
(cherry picked from commit 532961fad2)
2022-03-31 12:32:41 +00:00
Saqib Ansari
8416dc713c fix: unexpected keyword argument 'pluck' 2022-03-31 16:32:01 +05:30
Saqib Ansari
8d315a6573 fix: 'int' object has no attribute 'is_draft' 2022-03-31 16:17:51 +05:30
marination
a9ec72d833 chore: Added BOM std filters and update type in List View
(cherry picked from commit 2fece523f6)
2022-03-31 10:25:51 +00:00
marination
770f8da792 test: Added test for 2 more validations
- Covers full validate function

(cherry picked from commit a945484af4)
2022-03-31 10:25:51 +00:00
marination
0d3c8e4d74 fix: Type Annotations, Redundancy, etc.
- Renamed public function`update_new_bom` to `update_new_bom_in_bom_items`
- Replaced `get_cached_doc` with `get_doc`
- Removed click progress bar (drive through update log)
- Removed `bom_obj.update_new_bom()`, was redundant. Did same job as `update_new_bom_in_bom_items`
- Removed `update_new_bom()` in `bom.py`, unused.
- Prettier query formatting
- `update_type` annotated as non optional Literal
- Removed redundant use of JobTimeoutException
- Corrected type annotations in `create_bom_update_log()`

(cherry picked from commit 620575a901)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
2022-03-31 10:25:50 +00:00
marination
c0c39f8c79 fix: Semgrep
- Explain explicit commits and skip semgrep
- Format client side translated string correctly

(cherry picked from commit ebf00946c9)
2022-03-31 10:25:48 +00:00
marination
1f9ecb3397 fix: Auto format bom_update_log.py
(cherry picked from commit 79495679e2)
2022-03-31 10:25:47 +00:00
marination
9b069ed04b test: API hit via BOM Update Tool
- test creation of log and it's impact

(cherry picked from commit 1d1e925bcf)
2022-03-31 10:25:47 +00:00
Marica
70485a6afc Merge pull request #30517 from frappe/mergify/bp/version-13-hotfix/pr-30509
fix: Add non-existent Item check and cleanup in `validate_for_items` (backport #30509)
2022-03-31 15:55:46 +05:30
marination
5dca5563ff fix: Test, Sider and Added button to access log from Tool
(cherry picked from commit f3715ab382)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
2022-03-31 10:25:45 +00:00
marination
8b5e759965 fix: Sider and Linter
(cherry picked from commit 3e3af95712)
2022-03-31 10:25:43 +00:00
marination
444af4588f feat: List View indicators for Log and Error Log link in log
(cherry picked from commit 8aff75f8e8)
2022-03-31 10:25:42 +00:00
marination
59af556241 chore: Polish error handling and code sepration
- Added Typing
- Moved all job business logic to bom update log
- Added `run_bom_job` that handles errors and runs either of two methods
- UX: Replace button disabled until both inputs are filled
- Show log creation message on UI for correctness
- APIs return log document as result
- Converted raw sql to QB

(cherry picked from commit cff91558d4)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
2022-03-31 10:25:41 +00:00
marination
7aa37ec511 feat: BOM Update Log
- Created BOM Update Log that will handle queued job status and failures
- Moved validation and BG job to thus new doctype
- BOM Update Tool only works as an endpoint

(cherry picked from commit 4283a13e5a)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
2022-03-31 10:25:39 +00:00
marination
7317a0696b refactor: Add exception handling in background job within BOM Update Tool
(cherry picked from commit f57725f8fa)
2022-03-31 10:25:37 +00:00
marination
40a154e64d fix: (test) change expected exception due to https://github.com/frappe/frappe/pull/16454
(cherry picked from commit 93f6346cea)
2022-03-31 09:23:47 +00:00
marination
84247e91f3 fix: Add non-existent Item check and cleanup in validate_for_items
- Added a validation if invalid item code ia passed via data import/API, etc.
- Used DB APIs instead of raw sql
- Separated checks into separate functions
- Added return types to functions
- Better variable naming and removed redundant utils import in function

(cherry picked from commit 982a246eec)
2022-03-31 09:23:46 +00:00
Saqib Ansari
ab7417c26a fix: invalid keyword argument 'pluck' 2022-03-31 14:38:40 +05:30
Saqib Ansari
0bafec2384 fix: merge conflicts 2022-03-31 14:24:42 +05:30
Saqib Ansari
a51b32b7e0 fix: merge conflicts 2022-03-31 14:19:53 +05:30
Saqib Ansari
47567c66c1 chore: ignore rules for QB formatting
(cherry picked from commit e0c36d87e0)
2022-03-31 08:47:20 +00:00
Saqib Ansari
82aea2b998 fix: set is_return & return_against in POS Invoice Reference table
(cherry picked from commit 16253a2f72)

# Conflicts:
#	erpnext/patches.txt
2022-03-31 08:47:20 +00:00
Saqib Ansari
cf3e09588f fix: test cases
(cherry picked from commit 1b556d1c53)
2022-03-31 08:47:18 +00:00
Saqib Ansari
650274e973 fix: sider issues
(cherry picked from commit cb4873c019)
2022-03-31 08:47:17 +00:00
Saqib Ansari
3bb0716dff fix(pos): cannot close the pos if sr. no. is sold & returned
(cherry picked from commit cf51a0a1b8)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
#	erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
2022-03-31 08:47:17 +00:00
Saqib Ansari
36845a87e0 fix(pos): remove returned sr. nos. from pos reserved sr. nos. list
(cherry picked from commit f2ae63cbfd)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.py
#	erpnext/stock/doctype/serial_no/serial_no.py
2022-03-31 08:47:15 +00:00
Saqib Ansari
76f83ea559 fix(pos): allow validating stock on save
(cherry picked from commit aff7408775)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.py
2022-03-31 08:47:13 +00:00
Saqib Ansari
3b583c6c48 fix(pos): specific case when serialized item not removed
(cherry picked from commit 4afb47e869)
2022-03-31 08:47:11 +00:00
Saqib Ansari
a3a7dc9ce6 fix(pos): customer group filter in customer selector
(cherry picked from commit 2f82e237ef)
2022-03-31 08:47:10 +00:00
Saqib Ansari
bd2061d6f3 fix: prevent multiple save on applying coupon code
(cherry picked from commit d5fd8e0ba6)
2022-03-31 08:47:09 +00:00
Saqib Ansari
b4a10d571f fix(test): Item MacBook does not exist
(cherry picked from commit 4623a1bc57)
2022-03-31 08:46:17 +00:00
Saqib Ansari
c36b5d9ab8 perf: skip warehouse validation for non-stock items
(cherry picked from commit 199a6da960)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2022-03-31 08:46:16 +00:00
Saqib Ansari
ad91d57a41 perf: skip warehouse validation for non-stock items
(cherry picked from commit 6528218ac3)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2022-03-31 08:46:15 +00:00
Saqib Ansari
00cb0d0294 fix(asset): do not validate warehouse on asset purchase
(cherry picked from commit 136466d255)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2022-03-31 08:46:13 +00:00
Deepesh Garg
ebb2e975cd fix: make test record
(cherry picked from commit d93edbc859)
2022-03-31 08:10:25 +00:00
Deepesh Garg
168cc353ca fix: Account currency validation
(cherry picked from commit d4cc7c553e)
2022-03-31 08:10:25 +00:00
Anupam
444625a0ca fix: linter issues 2022-03-31 13:38:37 +05:30
Saqib Ansari
9ffe34757e Merge pull request #30455 from frappe/mergify/bp/version-13-hotfix/pr-30419
fix: move item tax to item tax template patch (backport #30419)
2022-03-31 12:43:15 +05:30
Anupam
af2d55f893 Merge branch 'crm-contract-naming-v13' of github.com:anupamvs/erpnext into crm-contract-naming-v13 2022-03-31 12:05:46 +05:30
Anupam
20ef6ab5bf fix: review changes 2022-03-31 12:04:53 +05:30
Saqib Ansari
1a169c9dc5 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30419 2022-03-31 12:02:15 +05:30
mergify[bot]
22ec1a4996 fix: enable row deletion in reference table (#30492)
(cherry picked from commit 500870b2b0)

Co-authored-by: rahib-hassan <rahib.hassan10@gmail.com>
2022-03-30 16:37:00 +05:30
Anoop
3f3717952c fix: cast array slice index integer while splitting serial_nos array (#30468) 2022-03-30 16:36:15 +05:30
mergify[bot]
c7d8d60de6 fix: explicitly check if additional salary is recurring while fetching components for payroll (backport #30489) (#30491)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-30 15:07:56 +05:30
Saqib Ansari
6397456ba6 Merge pull request #30484 from frappe/mergify/bp/version-13-hotfix/pr-30470
fix: credit limit validation in delivery note (backport #30470)
2022-03-30 14:57:49 +05:30
mergify[bot]
46e6d16c49 fix: Dont set idx while adding WO items to Stock Entry (backport #30377) (#30485)
* fix: Dont set `idx` while adding WO items to Stock Entry

- `idx` must be computed by base document's `self.append()` function, so do not set it

(cherry picked from commit a787ebb732)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py

* chore: Remove redundant idx query and value setting

- idx can be removed from `select_columns` as it is already in the main query
- setting idx to '' is not required as it is not used further

(cherry picked from commit 639d380c1f)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py

* test: idx mapping correctness

(cherry picked from commit fa3b953cf7)

* fix: Linter

(cherry picked from commit b5ad626d23)

* fix: resolve conflicts

Co-authored-by: marination <maricadsouza221197@gmail.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-30 12:05:26 +05:30
Saqib Ansari
c9cd3ee9aa fix: credit limit validation in delivery note
(cherry picked from commit c122882884)
2022-03-30 05:39:15 +00:00
Ankush Menat
7a2729e5bb Merge branch 'version-13-hotfix' into version-13-pre-release 2022-03-29 18:41:22 +05:30
mergify[bot]
39ff7b0b06 fix: validate 0 transfer qty in stock entry (#30476) (#30479)
(cherry picked from commit b80fac03af)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-29 18:18:13 +05:30
Ankush Menat
eedfee367c Merge pull request #30478 from ankush/format_v13
style: bulk formatting (v13 port)
2022-03-29 17:51:30 +05:30
Ankush Menat
14c8386040 chore: ignore black formatting commit in blame 2022-03-29 17:31:43 +05:30
Ankush Menat
c07713b860 style: bulk format code with black
v13 port because otherwise backports will result in conflicts always
2022-03-29 17:29:34 +05:30
Ankush Menat
7cc84dcbb4 ci: pin click for black (#30464)
[skip ci]
2022-03-29 17:26:42 +05:30
Ankush Menat
68ded18a23 ci: force black formatting (pre-commit) 2022-03-29 17:26:26 +05:30
mergify[bot]
00ad515ac7 fix: use name for links not item_code (backport #30462) (#30463)
* fix: use `name` for links not `item_code` (#30462)

(cherry picked from commit 76dce2eddc)

# Conflicts:
#	erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py

* fix: conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-29 17:16:31 +05:30
Deepesh Garg
751a6354f1 Merge pull request #30471 from deepeshgarg007/release_v13_24_0
chore: version bump an change log for v13.24.0
2022-03-29 14:35:21 +05:30
Deepesh Garg
33071a4731 Merge pull request #30469 from frappe/version-13-pre-release
chore: v13.24.0 release
2022-03-29 14:29:09 +05:30
Deepesh Garg
e1a0f067b0 chore: version bump an change log for v13.24.0 2022-03-29 14:18:37 +05:30
mergify[bot]
6b3bdb2e9e fix: dont check for failed repost while freezing (backport #30472) (#30473)
* fix: dont check for failed repost while freezing (#30472)

[skip ci]

(cherry picked from commit b12fe0f15b)

# Conflicts:
#	erpnext/stock/utils.py

* fix: conflicts

[skip ci]

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-29 13:56:15 +05:30
Deepesh Garg
a7b2fa31a1 chore: version bump an change log for v13.24.0 2022-03-29 13:38:39 +05:30
Deepesh Garg
4ee08e390a Merge pull request #30460 from frappe/mergify/bp/version-13-pre-release/pr-30385
fix(India): Auto tax fetching based on GSTIN (backport #30385)
2022-03-29 12:32:10 +05:30
Anupam Kumar
825fea0883 Merge branch 'version-13-hotfix' into crm-contract-naming-v13 2022-03-29 12:11:57 +05:30
Deepesh Garg
504f213742 fix: Resolve conflicts 2022-03-29 10:35:44 +05:30
Deepesh Garg
3ff77f9cb3 fix(India): Auto tax fetching based on GSTIN
(cherry picked from commit 7cae669e81)

# Conflicts:
#	erpnext/patches.txt
2022-03-29 04:57:17 +00:00
Deepesh Garg
8e59e67197 Merge pull request #30385 from deepeshgarg007/quotation_gst
fix(India): Auto tax fetching based on GSTIN
2022-03-29 10:25:57 +05:30
mergify[bot]
659fa7a190 fix: bom valuation - handle lack of LPP (#30454) (#30457)
(cherry picked from commit 8dff4d66a4)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-28 18:36:12 +05:30
Raffael Meyer
471a3279b9 fix(lead): reload address and contact before updating their links (Backport #29966) (#29968)
* fix(lead): reload address and contact before updating their links

They might have changed since they were created. Backport of #29966.

* refactor: reload contact after insert
2022-03-28 18:01:00 +05:30
Saqib Ansari
3142e6f188 fix: move item tax to item tax template patch
(cherry picked from commit 936267c934)
2022-03-28 12:05:23 +00:00
Deepesh Garg
ea2b0e224e Merge pull request #30451 from frappe/version-13-hotfix
chore: Pre release for v13.24.0
2022-03-28 16:59:18 +05:30
Anupam
1efb317243 feat: configurable Contract naming 2022-03-28 14:36:43 +05:30
Ankush Menat
7f3651a938 fix: update picked qty on cancellation
(cherry picked from commit 69ae2661d2)
2022-03-28 10:44:53 +05:30
Deepesh Garg
8fb56d57bb Merge pull request #30438 from deepeshgarg007/amount_to_pay
fix: Incorrect default amount to pay for POS invoices
2022-03-27 18:00:42 +05:30
Deepesh Garg
156416bcc3 Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into quotation_gst 2022-03-27 17:28:42 +05:30
Deepesh Garg
a044e92687 fix: Incorrect default amount to pay for POS invoices 2022-03-27 13:02:31 +05:30
Saqib Ansari
99dc1ecd2d Merge pull request #30435 from frappe/mergify/bp/version-13-hotfix/pr-30420
fix: unsupported operand type(s) for +=: 'int' and 'NoneType' (backport #30420)
2022-03-26 20:00:07 +05:30
Saqib Ansari
bd171fbd42 fix: unsupported operand type(s) for +=: 'int' and 'NoneType'
(cherry picked from commit 5450542fdb)
2022-03-26 13:46:24 +00:00
Rucha Mahabal
020a4269ae fix(Expense Claim): validate advances after setting totals 2022-03-25 23:43:46 +05:30
Rucha Mahabal
84de986c5a fix(test): add function for getting advances for claim 2022-03-25 23:43:46 +05:30
Rucha Mahabal
e783e3f93e fix: do not update status to Paid if sanctioned amount is 0 2022-03-25 23:43:46 +05:30
Rucha Mahabal
ed0a685e0e chore: sort imports 2022-03-25 23:43:46 +05:30
Rucha Mahabal
7d3c211b1f test: expense claim against fully and partially paid advances 2022-03-25 23:43:46 +05:30
Rucha Mahabal
961e691bdc fix: Expense Claim conditions for Paid status
- status doesn't change to Paid if the entire required amount is already covered via linked advances

- status doesn't change to Paid if the claim is partially paid via advances

- patch to update such uncancelled claims to Paid
2022-03-25 23:43:46 +05:30
Deepesh Garg
5308bb05fc chore: version bump 2022-03-25 21:57:48 +05:30
Deepesh Garg
985e3007e7 Merge pull request #30432 from frappe/mergify/bp/version-13/pr-30429
fix: Check for onload property (backport #30429)
2022-03-25 21:55:47 +05:30
Deepesh Garg
d6703cef55 Merge pull request #30431 from frappe/mergify/bp/version-13-pre-release/pr-30429
fix: Check for onload property (backport #30429)
2022-03-25 21:55:20 +05:30
Deepesh Garg
430c9c9df4 fix: Check for onload property
(cherry picked from commit 71402b43a7)
2022-03-25 16:24:24 +00:00
Deepesh Garg
071064c097 fix: Check for onload property
(cherry picked from commit 71402b43a7)
2022-03-25 16:24:02 +00:00
Deepesh Garg
c2b2b3962a Merge pull request #30429 from deepeshgarg007/transaction_issue
fix: Check for onload property
2022-03-25 21:51:07 +05:30
Deepesh Garg
71402b43a7 fix: Check for onload property 2022-03-25 21:49:19 +05:30
Deepesh Garg
0740158431 Merge pull request #30395 from deepeshgarg007/pos_write_off
fix: Write off amount wrongly calculated in POS Invoice
2022-03-25 20:56:19 +05:30
mergify[bot]
8b5fe164fd fix: show subassembly table always (#30422) (#30423)
[skip ci]

(cherry picked from commit 0534cf6c9c)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-25 19:04:22 +05:30
Deepesh Garg
6a50f36b31 test: test for auto write-off amount 2022-03-25 18:02:14 +05:30
Deepesh Garg
665bd4c510 Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into pos_write_off 2022-03-25 16:00:33 +05:30
Deepesh Garg
04a25b0e19 chore: version bump 2022-03-25 13:07:45 +05:30
Deepesh Garg
c2911998a1 Merge pull request #30415 from frappe/mergify/bp/version-13/pr-30406
fix: Rate change issue on save and mapping from other doc (backport #30406)
2022-03-25 13:02:17 +05:30
Deepesh Garg
73d4031100 Merge pull request #30414 from frappe/mergify/bp/version-13-pre-release/pr-30406
fix: Rate change issue on save and mapping from other doc (backport #30406)
2022-03-25 13:02:01 +05:30
Deepesh Garg
f892eb588a fix: Condition
(cherry picked from commit 2e0e6ca6b1)
2022-03-25 07:31:37 +00:00
Deepesh Garg
858fd6fce3 fix: Revert rate calculation
(cherry picked from commit 6937a498e7)
2022-03-25 07:31:36 +00:00
Deepesh Garg
f40c5cb681 fix: Add back calculation for discount
(cherry picked from commit 067564bd26)
2022-03-25 07:31:36 +00:00
Deepesh Garg
8ff47999ab fix: Rate change issue on save and mapping from other doc
(cherry picked from commit 13fcda5776)
2022-03-25 07:31:36 +00:00
Deepesh Garg
def18f2b13 fix: Condition
(cherry picked from commit 2e0e6ca6b1)
2022-03-25 07:31:08 +00:00
Deepesh Garg
6c23dea716 fix: Revert rate calculation
(cherry picked from commit 6937a498e7)
2022-03-25 07:31:08 +00:00
Deepesh Garg
4737cb10f3 fix: Add back calculation for discount
(cherry picked from commit 067564bd26)
2022-03-25 07:31:07 +00:00
Deepesh Garg
13e73f6a38 fix: Rate change issue on save and mapping from other doc
(cherry picked from commit 13fcda5776)
2022-03-25 07:31:07 +00:00
Deepesh Garg
26b90ffce0 Merge pull request #30406 from deepeshgarg007/rate_change_fixes
fix: Rate change issue on save and mapping from other doc
2022-03-25 12:59:40 +05:30
mergify[bot]
29fde6ee8b fix: failing broken patches (#30409) (#30412)
* fix: failing broken patches

* refactor: simpler conditions

[skip ci]

(cherry picked from commit c4854bb1b1)

Co-authored-by: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com>
2022-03-25 12:57:05 +05:30
Deepesh Garg
2e0e6ca6b1 fix: Condition 2022-03-25 12:39:59 +05:30
Deepesh Garg
6937a498e7 fix: Revert rate calculation 2022-03-25 12:28:55 +05:30
Deepesh Garg
067564bd26 fix: Add back calculation for discount 2022-03-25 12:17:51 +05:30
Deepesh Garg
13fcda5776 fix: Rate change issue on save and mapping from other doc 2022-03-25 10:46:09 +05:30
Ankush Menat
20794ac9ce fix: consider all existing PO items
When this is sent from API/client side doesn't send temporary_name it
can be flaky. Hence, use all available names for validation.
2022-03-24 18:00:00 +05:30
mergify[bot]
03d7fc1570 fix: broken production item links on production plan (backport #30399) (#30400)
* fix: only validate qty for main non-subassy items

(cherry picked from commit 3d43c437ad)

* fix: subassembly items linked to temporary name

Production Plan tables for po_items and sub_assembly_items are prepared
client side so both dont exist at time of first save or modifying and
hence any "links" created are invalid. This change retains temporary
name so it can be relinked server side after naming is performed.

Co-Authored-By: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 5b1d6055e6)

* test: dont resubmit WO

[skip ci]

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-24 17:30:58 +05:30
Deepesh Garg
d3fd2fd2c5 fix: Ignore for Purchase Invoices 2022-03-24 13:35:32 +05:30
Deepesh Garg
ed38679d22 fix: Ignore for Purchase Invoices 2022-03-24 13:09:55 +05:30
mergify[bot]
26fe2388e1 fix: (ux) Add is_group=0 filter on website warehouse (#30396) (#30397)
- It does not support group warehouses right now and it is misleading

(cherry picked from commit d24458ab77)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2022-03-24 13:01:06 +05:30
Deepesh Garg
2e33e748ea fix: Client side changes for POS Write off amount 2022-03-24 12:56:22 +05:30
Deepesh Garg
ee4258e42c fix: Write off amount wrongly calculated in POS Invoice 2022-03-24 12:31:37 +05:30
Deepesh Garg
e63f20b3f7 Merge pull request #30389 from frappe/mergify/bp/version-13-hotfix/pr-30334
fix: Reset GST State number (backport #30334)
2022-03-24 10:11:13 +05:30
Deepesh Garg
cd20502c7e fix: Reset GST State number
(cherry picked from commit 363e676a35)
2022-03-23 18:00:47 +00:00
Deepesh Garg
aded1fca3e Merge pull request #30343 from deepeshgarg007/item_tax_template_bug
fix: Taxes not getting fetched from item tax template
2022-03-23 22:02:17 +05:30
Deepesh Garg
7cae669e81 fix(India): Auto tax fetching based on GSTIN 2022-03-23 18:47:58 +05:30
Ankush Menat
54e4ff19ee fix: make auto created job cards "Open"
(cherry picked from commit 6418dc1606)
2022-03-23 16:46:22 +05:30
Ankush Menat
ff38d0498b fix: material transfer status should be before WIP
(cherry picked from commit e1f5620654)
2022-03-23 16:46:22 +05:30
Ankush Menat
a1740b0762 fix: ignore items without info for pending qty computation
(cherry picked from commit 4aa74af90d)
2022-03-23 15:51:29 +05:30
Ankush Menat
277c03e157 chore(DX): add sourceURL for debugging
(cherry picked from commit be16fb9dbb)
2022-03-23 15:51:29 +05:30
Deepesh Garg
67b9f43330 Merge pull request #30367 from frappe/mergify/bp/version-13-hotfix/pr-30365
fix: Changing item prices on converting orders/receipts to invoices (backport #30365)
2022-03-23 14:27:22 +05:30
Deepesh Garg
a5b29c1722 Merge pull request #30373 from frappe/mergify/bp/version-13/pr-30368
fix: Changing item prices on converting orders/receipts to invoices (backport #30365) (backport #30368)
2022-03-23 14:22:16 +05:30
Deepesh Garg
14ac2d39ab fix: Check if onload property exists in the form object
(cherry picked from commit 41b2d98c3f)
2022-03-23 08:48:17 +00:00
Deepesh Garg
740a9da1ee fix: Add ignore pricelist for missing flows
(cherry picked from commit 405dcb1d3c)
(cherry picked from commit 7d3cdf527b)
2022-03-23 08:48:17 +00:00
Deepesh Garg
c44231a432 fix: Changing item prices on converting orders/receipts to invoices
(cherry picked from commit 66ca085e39)
(cherry picked from commit a9533ef814)
2022-03-23 08:48:17 +00:00
Deepesh Garg
8fe9dbc75c Merge pull request #30368 from frappe/mergify/bp/version-13-pre-release/pr-30365
fix: Changing item prices on converting orders/receipts to invoices (backport #30365)
2022-03-23 14:17:56 +05:30
Deepesh Garg
a3f4069ad5 fix: Check if onload property exists in the form object 2022-03-23 13:58:21 +05:30
Deepesh Garg
41b2d98c3f fix: Check if onload property exists in the form object 2022-03-23 13:57:23 +05:30
Deepesh Garg
ebcd85e675 Merge pull request #30371 from frappe/mergify/bp/version-13-hotfix/pr-30361
fix: GST account not showing up in tax templates (backport #30361)
2022-03-23 13:42:44 +05:30
Deepesh Garg
0979ba0670 fix: GST account not showing up in tax templates
(cherry picked from commit 57924599da)
2022-03-23 07:47:52 +00:00
Deepesh Garg
7d3cdf527b fix: Add ignore pricelist for missing flows
(cherry picked from commit 405dcb1d3c)
2022-03-23 04:40:53 +00:00
Deepesh Garg
a9533ef814 fix: Changing item prices on converting orders/receipts to invoices
(cherry picked from commit 66ca085e39)
2022-03-23 04:40:52 +00:00
Deepesh Garg
0e3d0b0b03 fix: Add ignore pricelist for missing flows
(cherry picked from commit 405dcb1d3c)
2022-03-23 04:40:23 +00:00
Deepesh Garg
9f2868e858 fix: Changing item prices on converting orders/receipts to invoices
(cherry picked from commit 66ca085e39)
2022-03-23 04:40:22 +00:00
Ankush Menat
1b469bc341 fix: consider full integer batch nos
(cherry picked from commit 41db43cdc5)
2022-03-22 23:37:31 +05:30
Ankush Menat
4e3bb12b3a fix: ignore already fetched serial no
exclude_sr_nos is sent as JSON string of list, so load it before
operating on it.

(cherry picked from commit a18c687844)
2022-03-22 23:37:31 +05:30
Marica
315599b46f Merge pull request #30355 from frappe/mergify/bp/version-13-hotfix/pr-30336
fix: Product Filters Lookup (backport #30336)
2022-03-22 18:05:17 +05:30
mergify[bot]
7e6131280f fix: flaky salary slip email test (backport #30358) (#30360)
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-22 17:55:26 +05:30
marination
cfce719a7d fix: Add temporary text-md variable to maintain placeholder font size in input fields
- font is unusally large for filter lookup input
2022-03-22 16:12:30 +05:30
mergify[bot]
e29757f023 test: get leave allocation records query (backport #30342) (#30353)
Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-22 15:44:45 +05:30
Marica
0f774f2f07 fix: Merge conflicts in macros
- Use `filter-lookup-wrapper` on checkbox wrapper
- Bind data value to wrapper
2022-03-22 15:39:36 +05:30
marination
37134b04d8 fix: Product Filters Lookup
- bind the right classes to the filter lookup field
- make class names more descriptive
- make filter lookup field more visible with white bg and border
- bind lookup input field js in `views.js`
- make filter lookup field functioning for atribute filters too
- added placeholder to lookup field

(cherry picked from commit f6e64c2cac)

# Conflicts:
#	erpnext/templates/includes/macros.html
2022-03-22 10:02:28 +00:00
Deepesh Garg
7cba4975ef fix: Taxes not getting fetched from item tax template 2022-03-21 21:28:41 +05:30
mergify[bot]
3ca90cc3c7 fix(ux): warning for disabled carry forwarding in Policy Assignment (backport #30331) (#30333)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-21 15:53:08 +05:30
mergify[bot]
34d27f1855 fix: clear "Retain Sample" and "Max Sample Quantity" in Item card if Has Batch No is uncheck (backport #30307) (#30314)
* fix: clear "Retain Sample" and "Max Sample Quantity" in Item card if Has Batch No is uncheck (#30307)

(cherry picked from commit ca8d757691)

# Conflicts:
#	erpnext/stock/doctype/item/test_item.py

* chore: conflicts

* refactor: correct usage for test decorator

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-21 15:39:04 +05:30
mergify[bot]
684e83faac fix: Future recurring period calculation for additional salary (#29581) (#30329)
* fix: Future recurring period calculation for addl salary

* fix: future recurring period calculation

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
(cherry picked from commit cedabd7242)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2022-03-21 15:00:07 +05:30
mergify[bot]
2148e7e32d fix: Payment Request Amount calculation in case of multicurrency (#30254) (#30326)
(cherry picked from commit 9abd22b408)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2022-03-21 14:57:25 +05:30
Nabin Hait
cedabd7242 fix: Future recurring period calculation for additional salary (#29581)
* fix: Future recurring period calculation for addl salary

* fix: future recurring period calculation

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-21 14:23:31 +05:30
Nabin Hait
c5f257da00 fix: While creating Payment Request from other forms, open a new Payment Request form without saving (#30228) 2022-03-21 14:22:10 +05:30
Nabin Hait
9abd22b408 fix: Payment Request Amount calculation in case of multicurrency (#30254) 2022-03-21 14:20:15 +05:30
mergify[bot]
71d6209a29 fix: disable deferred naming on SLE/GLE if hash method is used. (#30286) (#30315)
* fix: dont rename GLE/SLE that dont have naming series

* test: tests for deferred naming of ledgers

(cherry picked from commit c2aad115c1)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-21 12:29:21 +05:30
Deepesh Garg
621db33591 Merge pull request #30321 from frappe/mergify/bp/version-13-hotfix/pr-30317
fix: P&L account validation on cancellation (backport #30317)
2022-03-21 12:05:41 +05:30
Deepesh Garg
4337b86349 Merge pull request #30318 from frappe/mergify/bp/version-13-hotfix/pr-30304
fix: Add permission for KSA VAT documents (backport #30304)
2022-03-21 12:05:15 +05:30
Saqib Ansari
10f06d2e50 Merge pull request #30320 from frappe/mergify/bp/version-13-hotfix/pr-30287
fix: custom cash flow mapper doesn't show any data (backport #30287)
2022-03-21 11:40:06 +05:30
Deepesh Garg
1b5e936d27 fix: P&L account validation on cancellation
(cherry picked from commit 2ff6790283)
2022-03-21 05:15:39 +00:00
Saqib Ansari
8f6333cd40 refactor: convert raw sql to frappe.qb
(cherry picked from commit 00bfee97c7)
2022-03-21 05:03:19 +00:00
Saqib Ansari
4dd6a99b69 fix: custom cash flow mapper doesn't show any data
(cherry picked from commit 119273e633)
2022-03-21 05:03:19 +00:00
Ankush Menat
5ab6e3fc85 chore: bump version 2022-03-21 10:31:24 +05:30
Ankush Menat
84c5f98412 Merge branch 'version-13-pre-release' into version-13 2022-03-21 10:30:52 +05:30
Deepesh Garg
da09b64174 fix: Conflicts 2022-03-21 10:28:58 +05:30
Deepesh Garg
75458f90b5 fix: Add permission for KSA Vat documents
(cherry picked from commit 972d06555a)

# Conflicts:
#	erpnext/patches.txt
2022-03-21 04:49:05 +00:00
Syed Mujeer Hashmi
dc8674fe0d fix: Allow draft PE, PA to link to Vital Signs (#29934)
Signed-off-by: Syed Mujeer Hashmi <mujeerhashmi@4csolutions.in>

Co-authored-by: Chillar Anand <chillar@avilpage.com>
Co-authored-by: Chillar Anand <anand21nanda@gmail.com>
2022-03-21 08:42:28 +05:30
mergify[bot]
3636e596d3 refactor: remove redundant if-statement (#30311) (#30316)
(cherry picked from commit 0a015b7f70)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2022-03-20 20:24:40 +05:30
mergify[bot]
7bcad01953 fix(UX): misc serial no selector + warehouse.py refactor (backport #30309) (#30310)
* fix: set current qty as default qty for stock entry

(cherry picked from commit f4c213379e)

* fix: filter serial nos by selected batch number

(cherry picked from commit 5ec27c9055)

* fix: skip already selected serials in sr selector

(cherry picked from commit 0a533d6ccc)

* fix: sort serial nos before sending

(cherry picked from commit 4f8bb91eae)

* test: auto serial fetching

(cherry picked from commit b9eec331e3)

* refactor: batch no filter handling

(cherry picked from commit a585dff6fd)

# Conflicts:
#	erpnext/stock/doctype/serial_no/serial_no.py

* refactor: Use QB for serial fetching query

(cherry picked from commit 4b695915f4)

# Conflicts:
#	erpnext/stock/doctype/serial_no/serial_no.py

* refactor(warehouse): raw query to ORM

(cherry picked from commit 953afda01b)

* test: warehouse conversion and treeview test

(cherry picked from commit 684d9d66d1)

* perf: Single query to delete bins instead of `N`

(cherry picked from commit 4859574233)

* chore: resolve conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-20 19:25:10 +05:30
Ankush Menat
230d171c58 chore: add change log for v13.23.0 2022-03-20 15:18:33 +05:30
Devarsh Bhatt
cca9668309 fix: Allow on Submit for Material Request Item Required Date (#30174)
* fix: Allow on Submit for Material Request Item Required Date

* chore: whitespace

Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2022-03-19 13:02:21 +05:30
Deepesh Garg
1c36b1a923 Merge pull request #30293 from frappe/mergify/bp/version-13-pre-release/pr-30284
fix: Cleanup and fixes in Dimension-wise Accounts Balance Report (backport #30284)
2022-03-18 17:37:12 +05:30
mergify[bot]
ec79e13b61 fix: respect db multi_tenancy while fetching precision (#30301) (#30303)
[skip ci]

(cherry picked from commit 5a9bf9ffd6)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-18 17:32:09 +05:30
mergify[bot]
3d35c69a2d fix: respect db multi_tenancy while fetching precision (#30301) (#30302)
[skip ci]

(cherry picked from commit 5a9bf9ffd6)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-18 17:32:01 +05:30
Deepesh Garg
88b2447297 Merge pull request #30296 from frappe/mergify/bp/version-13-hotfix/pr-30266
fix: Validate income/expense account in sales and purchase invoice (backport #30266)
2022-03-17 18:55:21 +05:30
Deepesh Garg
6c23b306d9 Merge pull request #30297 from frappe/mergify/bp/version-13-pre-release/pr-30266
fix: Validate income/expense account in sales and purchase invoice (backport #30266)
2022-03-17 18:54:50 +05:30
Deepesh Garg
0269bc9191 fix: Test case
(cherry picked from commit 4237e5d928)
2022-03-17 13:03:41 +00:00
Deepesh Garg
c0183289f5 fix: Validate income/expense account in sales and purchase invoice
(cherry picked from commit 06936cf1c0)
2022-03-17 13:03:41 +00:00
Deepesh Garg
2cf19aa19d fix: Test case
(cherry picked from commit 4237e5d928)
2022-03-17 13:03:20 +00:00
Deepesh Garg
935d2e3d64 fix: Validate income/expense account in sales and purchase invoice
(cherry picked from commit 06936cf1c0)
2022-03-17 13:03:20 +00:00
mergify[bot]
face53ae8e feat: Create single PL/DN from several SO. (backport #30238) (#30289)
* feat: Create single PL/DN from several SO. New PR from latest develop to avoid rebase

(cherry picked from commit 61eb9b6c68)

* to enable selection of SO irrespective of customer,removed validation for customer

(cherry picked from commit 466df6bdb7)

* fixed spacings

(cherry picked from commit f33a725a9e)

* added new field - Picked Qty, in Sales Order Item

(cherry picked from commit a68213d82e)

* Added new field in SO - % Picked

(cherry picked from commit 9f7fee7a4f)

* removed semicolon for break statement

(cherry picked from commit e970616b51)

* as per review comments - changed for loop

(cherry picked from commit 0211f27e83)

* corrected spacing

(cherry picked from commit a12895ec03)

Co-authored-by: Krithi Ramani <krithi.ramani@gmail.com>
2022-03-17 18:17:16 +05:30
Deepesh Garg
c20fc51921 Merge pull request #30294 from frappe/mergify/bp/version-13-pre-release/pr-30261
fix: Error in bank reconciliation statement (backport #30261)
2022-03-17 18:11:14 +05:30
Deepesh Garg
e60145e10a Merge pull request #30295 from frappe/mergify/bp/version-13-hotfix/pr-30261
fix: Error in bank reconciliation statement (backport #30261)
2022-03-17 18:11:04 +05:30
Deepesh Garg
aa1732a212 fix: Error in bank reco statement
(cherry picked from commit fbcb413d96)
2022-03-17 12:21:03 +00:00
Deepesh Garg
9e8b8305e9 fix: Error in bank reco statement
(cherry picked from commit fbcb413d96)
2022-03-17 12:00:05 +00:00
Deepesh Garg
fe88cfb80d fix: Remove comments
(cherry picked from commit cab69fe1f2)
2022-03-17 11:58:11 +00:00
Deepesh Garg
eac07238d9 fix: Clean and fixes in Dimension-wise Accounts Balance Report
(cherry picked from commit 08a06ce5c6)
2022-03-17 11:58:10 +00:00
Deepesh Garg
65d0189f71 Merge pull request #30284 from deepeshgarg007/dimension_wise_balance_report
fix: Clean and fixes in Dimension-wise Accounts Balance Report
2022-03-17 17:27:01 +05:30
Ankush Menat
d8b7693ea9 Merge pull request #30291 from ankush/13_23_pre_release
chore: merge version-13-hotfix to version-13-pre-release
2022-03-17 16:31:00 +05:30
mergify[bot]
3b1033c315 test: fix holiday list creation causing flaky tests (backport #30260) (#30288)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-17 15:39:02 +05:30
Deepesh Garg
cab69fe1f2 fix: Remove comments 2022-03-17 12:57:12 +05:30
Deepesh Garg
08a06ce5c6 fix: Clean and fixes in Dimension-wise Accounts Balance Report 2022-03-17 12:56:43 +05:30
Rucha Mahabal
15413cf835 fix: Non Profit fixes (#30280) 2022-03-17 12:52:02 +05:30
mergify[bot]
0becc1e231 refactor: call repost directly during tests (#30277) (#30279)
enqueue(now=frappe.flags.in_test) is always true in test, this change avoids
confusion.

(cherry picked from commit 76187d175f)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-16 23:01:38 +05:30
mergify[bot]
64b646050e refactor: removed unrequired code and test for standalone delivery note serial return (#30276) (#30278)
(cherry picked from commit 2a0ca7c91a)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
2022-03-16 19:51:22 +05:30
Ankush Menat
f3af5dd384 test: packed item return scenarios
(cherry picked from commit b46d6e3c05)
2022-03-16 14:02:17 +05:30
Ankush Menat
cde3c685ee revert: "fix: updated packed_items getting fetched on Sales Return / Credit Note (#28607)"
This reverts commit 20216fa9f1.

(cherry picked from commit b781e8b7d1)
2022-03-16 14:02:17 +05:30
Saqib Ansari
b8fc06f633 Merge pull request #30270 from frappe/mergify/bp/version-13-hotfix/pr-30269
fix: Do not update ignore pricing rule check implicitly (backport #30269)
2022-03-16 13:01:54 +05:30
Saqib Ansari
461506a7ff Merge pull request #30267 from frappe/mergify/bp/version-13-hotfix/pr-30265
feat: add German translations (backport #30265)
2022-03-16 13:01:32 +05:30
Deepesh Garg
f27fd605f5 fix: Do not update ignore prcing rule check implicitly
(cherry picked from commit 1f79b47a17)
2022-03-16 07:03:16 +00:00
Saqib Ansari
3bcef50b15 Merge pull request #30262 from frappe/mergify/bp/version-13-hotfix/pr-30244
fix: incorrect debit credit amount in presentation currency (backport #30244)
2022-03-16 10:55:04 +05:30
Samuel Danieli
29fa902b33 feat: add German translations
(cherry picked from commit bbe5739547)
2022-03-16 05:24:45 +00:00
Saqib Ansari
510668f78a test: foreign_account_balance_after_exchange_rate_revaluation
(cherry picked from commit 12d99ed69a)
2022-03-15 13:10:27 +00:00
Saqib Ansari
8663a776dd fix: incorrect debit credit amount in presentation currency
(cherry picked from commit 83a5fae591)
2022-03-15 13:10:26 +00:00
Marica
69bf64e238 Merge pull request #30259 from frappe/mergify/bp/version-13-hotfix/pr-30258
fix: Sub-Category Routing in Item Group Page Listing pills (backport #30258)
2022-03-15 17:47:40 +05:30
marination
a56fd20ab4 fix: Sub-Categpry Routing in Item Group Page Listing pills
- Use absolute route even 3-4 sub-category levels down
- Remove scroll from category pills due to accessibility issues
- Arrange sub-category pills alphabetically

(cherry picked from commit 8264d6b0bc)
2022-03-15 12:00:33 +00:00
mergify[bot]
2c5e76d542 fix(pos): loyalty points in case of returned pos invoice (#30257) 2022-03-15 17:28:21 +05:30
rohitwaghchaure
5d458d6eb7 Merge pull request #30243 from akurungadam/jc-fix
fix: Job Card sub operations status and list view
2022-03-15 15:20:56 +05:30
mergify[bot]
6ef08545b6 fix: Leave Policy Assignment creation patch (backport #30215) (#30252)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-15 12:24:08 +05:30
mergify[bot]
0133b2a145 fix: salary slip amount rounding errors (backport #30248) (#30251)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-15 11:41:38 +05:30
Deepesh Garg
f48caf4944 Merge pull request #30246 from frappe/mergify/bp/version-13-hotfix/pr-30230
fix: KSA E-Invoice QR Code showing wrong VAT amount (backport #30230)
2022-03-15 09:49:55 +05:30
Deepesh Garg
5e4ab346c7 Merge pull request #30245 from frappe/mergify/bp/version-13-hotfix/pr-30206
fix: Do not update ignore prcing rule check implicitly (backport #30206)
2022-03-15 09:49:35 +05:30
Deepesh Garg
ec92551f7c Update erpnext/regional/saudi_arabia/utils.py
Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
(cherry picked from commit e33d4713cd)
2022-03-14 16:17:46 +00:00
Deepesh Garg
c5de90320f fix: Itemised tax rate updation
(cherry picked from commit be56efad26)
2022-03-14 16:17:46 +00:00
Deepesh Garg
fc298770e9 fix: Linting Issue
(cherry picked from commit 3cc2e53b08)
2022-03-14 16:17:46 +00:00
Deepesh Garg
8b077106f9 fix: KSA E-Invoice QR Code showing wrong VAT amount
(cherry picked from commit b37559c535)
2022-03-14 16:17:45 +00:00
Deepesh Garg
b0e178a29d fix: Do not update ignore prcing rule check implicitly
(cherry picked from commit 96b5cedcf8)
2022-03-14 16:16:50 +00:00
Anoop Kurungadam
7b8723445e fix: job card - sub operations table status misbehaviour on pause / resume Job Card 2022-03-14 19:34:18 +05:30
Anoop Kurungadam
fa32fc3c83 fix: show status in job card list view in Draft mode 2022-03-14 19:33:58 +05:30
Ankush Menat
1dbb654500 test: refactor BOM quality template test 2022-03-14 18:56:33 +05:30
Florian HENRY
d8de8caa1e fix: BOM - clear Quality Inspection Template according to Inspection Quality Required
Squashed commit of the following:

commit b73fa210b234d2c8067db2c32f94f362b89afe5a
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Mon Mar 14 08:30:24 2022 +0100

    add json tes

commit 984d015a7d9aceb6ea64be7ed9f1cc0caa356714
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Mon Mar 14 08:30:06 2022 +0100

    better test

commit 42061146658598da02eda7cb781bd2cf44c8ca34
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Fri Mar 11 16:12:57 2022 +0100

    update test

commit 6259c0957566600b044fcd4a5e14e69cdff71020
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Fri Mar 11 13:59:13 2022 +0100

    update test

commit cbc4ad9566f6f7f01dd622651458fea309d80954
Merge: faa44b3fdd 94d0f8d4e7
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Fri Mar 11 12:46:48 2022 +0100

    Merge branch 'develop' of https://github.com/frappe/erpnext into dev_fix_30190

commit faa44b3fdd983adfc04bbf04a7ebfef114501bb3
Author: Florian HENRY <florian.henry@open-concept.pro>
Date:   Fri Mar 11 12:03:35 2022 +0100

    fix: clear Quality Inspection Template according to Inspection Quality Required
2022-03-14 18:56:33 +05:30
mergify[bot]
767a02cd15 fix: cannot create multicurrency sales order with product bundles (#30222) 2022-03-14 18:33:42 +05:30
mergify[bot]
34d6031601 fix(pos): do not reset mode of payments in case of consolidation (backport #30198) (#30218) 2022-03-14 18:30:41 +05:30
Marica
9b2cb23b22 Merge pull request #30232 from frappe/mergify/bp/version-13-hotfix/pr-30229
fix: Add missing currency option in Supplier Quotation's `rounded_total` field (backport #30229)
2022-03-14 17:22:06 +05:30
Ankush Menat
2d8bbf1112 Merge pull request #30237 from frappe/mergify/bp/version-13-hotfix/pr-30207
fix: Do not consider cancelled entries (backport #30207)
2022-03-14 17:20:46 +05:30
Deepesh Garg
f23810749d fix: Do not consider cancelled entries
(cherry picked from commit 6308e1be91)
2022-03-14 11:49:53 +00:00
Ankush Menat
4768f4f278 fix(ux): negative stock warning
(cherry picked from commit 02d64a32c2)
2022-03-14 17:18:06 +05:30
Ankush Menat
fab8e16537 Merge pull request #30235 from frappe/mergify/bp/version-13-pre-release/pr-30216
fix: max_qty validation condition in WO (backport #30216)
2022-03-14 17:09:24 +05:30
Sagar Sharma
46f9503a5c test: add test for planned_qty
(cherry picked from commit b22bdc5ff7)
2022-03-14 11:38:43 +00:00
Sagar Sharma
18d7c77bad fix: max_qty validation condition
(cherry picked from commit d198c488a4)
2022-03-14 11:38:43 +00:00
Ankush Menat
aab677c26f Merge pull request #30216 from s-aga-r/fix/production-plan/validation/max_qty
fix: max_qty validation condition in WO
2022-03-14 17:08:21 +05:30
marination
d5b142e5ca chore: Re-arrange fields for consistency with base currency LHS
(cherry picked from commit a579a211fd)
2022-03-14 11:31:19 +00:00
marination
017624c270 fix: Add missing currency option in Supplier Quotation's rounded_total field
(cherry picked from commit dcd88ddc87)
2022-03-14 11:31:19 +00:00
Sagar Sharma
b22bdc5ff7 test: add test for planned_qty 2022-03-14 16:28:23 +05:30
Ankush Menat
1fd96f379f test(refactor): use FrappeTestCase
(cherry picked from commit b0d1e6db54)
2022-03-14 16:20:04 +05:30
mergify[bot]
2e79c4fd59 fix(ux): remove get item buttons from submitted production plan (#30224)
(cherry picked from commit 1af13ca4bf)

fix(patch): remove dead links to ProdPlan Item

(cherry picked from commit d3e90ed8c8)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-14 15:17:46 +05:30
Ankush Menat
468964a3f7 ci: Revert fail on merge conflict label
This reverts commit d32f229483.
2022-03-14 14:43:15 +05:30
Nabin Hait
98a67967a3 fix: Search query of payroll entry reference in Journal Entry (#30225) 2022-03-14 14:40:00 +05:30
Saqib Ansari
75061cae66 Merge pull request #30220 from nextchamp-saqib/backport-#26872
fix: pos return payment mode issue (#26872)
2022-03-14 13:41:41 +05:30
Subin Tom
a1c9d1c67f fix: pos return payment mode issue (#26872) 2022-03-14 12:53:26 +05:30
Deepesh Garg
2e10e9ec33 Merge pull request #30210 from frappe/mergify/bp/version-13-hotfix/pr-30039
fix: Nil and Exempted values in GSTR-3B Report (backport #30039)
2022-03-14 12:51:31 +05:30
Deepesh Garg
5c285bc019 Merge pull request #30211 from frappe/mergify/bp/version-13-hotfix/pr-29979
fix: Multi-currency bank reconciliation fixes (backport #29979)
2022-03-14 12:51:14 +05:30
Ankush Menat
7aca949a33 fix(ux): skip items without batch series
(cherry picked from commit 941ea1ec74)
2022-03-14 12:50:59 +05:30
Ankush Menat
aadc2a8849 test: negative fifo test
(cherry picked from commit 91fd9d917a)
2022-03-14 12:50:59 +05:30
Saqib Ansari
1120a0f72f Merge pull request #30219 from frappe/mergify/bp/version-13-hotfix/pr-30217
fix: cannot create purchase order from sales order (backport #30217)
2022-03-14 12:45:51 +05:30
Saqib Ansari
1cee08f3e9 fix: cannot create purchase order from sales order
(cherry picked from commit 58804b8436)
2022-03-14 07:05:59 +00:00
Sagar Sharma
d198c488a4 fix: max_qty validation condition 2022-03-14 12:05:34 +05:30
Ankush Menat
fc257fa58d test: actual bom naming test
(cherry picked from commit 94d0f8d4e7)
2022-03-14 09:58:14 +05:30
Rohan Bansal
c8ca92e406 fix: cancelled document check
(cherry picked from commit 67d8a7ba86)
2022-03-14 09:58:14 +05:30
Ankush Menat
1a2a03ed98 refactor: split versioning code for testability
(cherry picked from commit 6b58d53403)
2022-03-14 09:58:14 +05:30
Rohan Bansal
6a031a87da fix: improve bom autoname logic
(cherry picked from commit 7f2670941c)
2022-03-14 09:58:14 +05:30
Ankush Menat
25df914bc6 test: bom for item_code that is >VARCHAR_LEN
(cherry picked from commit e2c99e02a9)
2022-03-14 09:58:14 +05:30
Rohan Bansal
404a82121d fix: if an item code is too long, truncate before setting BOM name
(cherry picked from commit d9c91748f4)
2022-03-14 09:58:14 +05:30
Rucha Mahabal
164d28a87a Merge pull request #30212 from frappe/mergify/bp/version-13-hotfix/pr-29439 2022-03-13 21:46:50 +05:30
Rucha Mahabal
465c117708 fix: conflicts 2022-03-13 21:05:06 +05:30
Rucha Mahabal
d50dbca844 fix: flaky tests
(cherry picked from commit d61c437588)
2022-03-13 15:26:33 +00:00
Rucha Mahabal
70febe468b fix: add more type hints
(cherry picked from commit 558650bc3a)
2022-03-13 15:26:33 +00:00
Rucha Mahabal
9449d11a4c fix: simplify insufficient leave balance message
(cherry picked from commit 8173e6a8ea)
2022-03-13 15:26:32 +00:00
Rucha Mahabal
c66a66e090 fix: conflicts
(cherry picked from commit b2c549a464)
2022-03-13 15:26:32 +00:00
Rucha Mahabal
d4301675d1 chore: linter issue
(cherry picked from commit 921d6b25d7)
2022-03-13 15:26:32 +00:00
Rucha Mahabal
fab0153e34 test: separate leave ledger entries for leave applications across allocations
(cherry picked from commit 97b7b5012e)
2022-03-13 15:26:31 +00:00
Rucha Mahabal
75833243ae fix: boundary determination for separate ledger entries
(cherry picked from commit 70239158b9)
2022-03-13 15:26:31 +00:00
Rucha Mahabal
b63cedc7d7 test: leave application validations
(cherry picked from commit 6755d6e6f5)
2022-03-13 15:26:31 +00:00
Rucha Mahabal
bfedac47e6 fix: clearer validation/warning messages for insufficient balance in leave application
(cherry picked from commit a504ffcc4c)
2022-03-13 15:26:31 +00:00
Rucha Mahabal
a30ec8bef6 feat: split ledger entries for applications created across allocations
- fix: ledger entry for expiring cf leaves not considering holidays

(cherry picked from commit c0f1e269e4)
2022-03-13 15:26:31 +00:00
Rucha Mahabal
51b17cfbc0 fix: add type hints for employee leave balance report
(cherry picked from commit 430bf00458)
2022-03-13 15:26:30 +00:00
Rucha Mahabal
4cd89c3dac test: get leave details for leave application dashboard
(cherry picked from commit 3f3b1766c2)

# Conflicts:
#	erpnext/hr/doctype/leave_application/test_leave_application.py
2022-03-13 15:26:30 +00:00
Rucha Mahabal
758e095ffd test: fix test test_leave_balance_near_allocaton_expiry
(cherry picked from commit a58dfecb23)
2022-03-13 15:26:30 +00:00
Rucha Mahabal
d9ec973096 fix: earned leave policy assignment test
(cherry picked from commit aaa1ae94f2)
2022-03-13 15:26:30 +00:00
Rucha Mahabal
b1d3d8226c fix: leave application dashboard
- total leaves allocated considering cancelled leaves

- optional plural for leave category labels

- show dashboard only once from date is set, else it fetches all allocations till date and generates incorrect balance

- change pending leaves to 'Leaves Pending Approval' for better context

- update labels in Salary Slip Leave Details table

(cherry picked from commit 942511cfff)
2022-03-13 15:26:29 +00:00
Rucha Mahabal
73b104aa45 test: Employee Leave Balance Summary
(cherry picked from commit 88141d6116)
2022-03-13 15:26:29 +00:00
Rucha Mahabal
49434e443c chore: remove unused imports, sort imports, fix sider
(cherry picked from commit c7d594984a)
2022-03-13 15:26:28 +00:00
Rucha Mahabal
6af8e9790b test: employee leave balance report
- fix expired leaves calculation when filters span across 2 different allocation periods

(cherry picked from commit c050ce49c2)

# Conflicts:
#	erpnext/payroll/doctype/salary_slip/test_salary_slip.py
2022-03-13 15:26:28 +00:00
Rucha Mahabal
e96b661ca1 fix: show actual balance instead of consumption balance in opening balance
- not changing opening balance based on remaining days

(cherry picked from commit dbfa463738)
2022-03-13 15:26:28 +00:00
Rucha Mahabal
420cb8269c fix: sort imports, sider issues
(cherry picked from commit b5c686ac40)
2022-03-13 15:26:28 +00:00
Rucha Mahabal
4632d13dca refactor: balance in Balance Summary report near allocation expiry date
- Leave Balance shows minimum leaves remaining after comparing with remaining days for allocation expiry causing ambiguity

- refactor remaining leaves calculation to return both, actual leave balance and balance for consumption

- show actual balance in leave application, use balance for consumption only in validations

(cherry picked from commit 55ac8519bf)
2022-03-13 15:26:27 +00:00
Rucha Mahabal
60eb38c911 refactor: Leaves Taken calculation
- fix issue where Leaves Taken also adds leaves expiring on boundary date as leaves taken, causing ambiguity

- remove unnecessary `skip_expiry_leaves` function

- `get_allocation_expiry` considering cancelled entries too

(cherry picked from commit 26b40e7cfd)
2022-03-13 15:26:27 +00:00
Rucha Mahabal
d7a75a695d fix: expired leaves not calculated properly because of newly created expiry ledger entries
(cherry picked from commit 1ea749cf1a)
2022-03-13 15:26:27 +00:00
Rucha Mahabal
f7b06a622f refactor: Employee Leave Balance Report
- incorrect opening balance on boundary allocation dates

- carry forwarded leaves accounted in leaves allocated column, should be part of opening balance

- leaves allocated column also adds expired leave allocations causing negative balances, should only consider non-expiry records

(cherry picked from commit 538b64b1fa)
2022-03-13 15:26:27 +00:00
Deepesh Garg
8a567b8d8c fix: Multi-currency bank reconciliation fixes
(cherry picked from commit cbb5ffb6fe)
2022-03-13 14:57:26 +00:00
Deepesh Garg
39d6cebf1a fix: Nil and Exempted values in GSTR-3B Report
(cherry picked from commit abe580e8b0)
2022-03-13 14:31:37 +00:00
Ankush Menat
2a00380e5b fix: attach sr no si standalone credit note
(cherry picked from commit 1a256c62c4)
2022-03-13 19:24:41 +05:30
Ankush Menat
625034a016 test: standalone SI creates and attaches serial nos
(cherry picked from commit 1c37d2711a)
2022-03-13 19:24:41 +05:30
Deepesh Garg
d71a8886b4 Merge pull request #30205 from frappe/mergify/bp/version-13-hotfix/pr-30152
fix: Sales and Purchase return optimization (backport #30152)
2022-03-13 17:49:19 +05:30
Deepesh Garg
abc2dd9e3e fix: Update modified timestamp
(cherry picked from commit a5befb6bf8)
2022-03-13 11:44:13 +00:00
Deepesh Garg
f31a7bde82 fix: Update party type
(cherry picked from commit e9d458b822)
2022-03-13 11:44:13 +00:00
Deepesh Garg
5eeb09c84e fix: Sales and Purchase retrun optimization
(cherry picked from commit 395b15058c)
2022-03-13 11:44:13 +00:00
Deepesh Garg
d47b3517f3 Merge pull request #30188 from frappe/mergify/bp/version-13-hotfix/pr-30181
fix: Shipping rule application fixes (backport #30181)
2022-03-13 17:14:07 +05:30
Deepesh Garg
bffd7feadb fix: Remove docstatus check during budget validate 2022-03-11 16:59:28 +05:30
Deepesh Garg
1fe608ea23 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-03-11 16:55:03 +05:30
Deepesh Garg
44fd94f6b2 fix: Shipping rule application fixes
(cherry picked from commit d596e0e4df)
2022-03-11 06:35:03 +00:00
Ankush Menat
a04c7fc27d test: submit PR directly
(cherry picked from commit 472fe8e319)
2022-03-10 18:27:57 +05:30
Ankush Menat
b13389b99a test: flaky MR report test
all test records are on same day so sorting was random in report rows.

(cherry picked from commit 6c54be0dcd)
2022-03-10 18:27:57 +05:30
Ankush Menat
e428c36053 fix: only update valuation rate if not None
(cherry picked from commit 7dd10367f4)
2022-03-10 18:27:57 +05:30
mergify[bot]
f06554236f fix: fetch new fields from routing to bom (backport #30169) (#30171)
* fix: fetch new fields in bom from routing

(cherry picked from commit 18e2a33a9b)

* fix: dont hardcode hour rate precision

(cherry picked from commit 362102e802)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_operation/bom_operation.json

* fix: resolve conflict

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-10 18:09:09 +05:30
Ankush Menat
33ddbb4f3c fix: handle duplicate bins during item merge renames
(cherry picked from commit 73901aad6f)
2022-03-10 17:23:23 +05:30
Ankush Menat
de5500817f test: refactor item merge test and disable commits
(cherry picked from commit b4d4ae6aa3)
2022-03-10 17:23:23 +05:30
mergify[bot]
a5b25db880 fix: dont reset UOM in MR on every get_item_detail call (#30164) (#30165)
(cherry picked from commit 4126455975)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-10 15:44:25 +05:30
Saqib Ansari
e89c181179 Merge pull request #30162 from frappe/mergify/bp/version-13-hotfix/pr-30103
fix: Ignore missing customer group while fetching price list (backport #30103)
2022-03-10 15:29:34 +05:30
Marica
584f0698ff Merge pull request #30163 from frappe/mergify/bp/version-13-hotfix/pr-30160
fix: 'save_quotations_as_draft' checkbox not honoured (backport #30160)
2022-03-10 15:12:02 +05:30
Marica
b529b5c0cb fix: Remove extra empty line after frappe imports 2022-03-10 14:53:48 +05:30
Marica
aec1916a0f fix: Merge Conflicts in test_shopping_cart imports 2022-03-10 14:49:05 +05:30
marination
62b50b6b63 fix: 'save_quotations_as_draft' checkbox not honoured
- Make sure `request_for_quotation` considers `save_quotations_as_draft`
- Added test for checkout disabled quote

(cherry picked from commit a13e06156b)

# Conflicts:
#	erpnext/e_commerce/shopping_cart/test_shopping_cart.py
2022-03-10 09:09:00 +00:00
mergify[bot]
4febcd86f8 fix: flaky tests (backport #30154) (#30161)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-10 14:38:08 +05:30
Saqib Ansari
3f0a4d9921 fix: Ignore missing customer group while fetching price list
(cherry picked from commit e5fb871ef4)
2022-03-10 09:06:42 +00:00
ChillarAnand
bb0be1699b fix: Ignore missing customer group while fetching price list
(cherry picked from commit 9ace7d606c)
2022-03-10 09:06:42 +00:00
Deepesh Garg
5e6879a7f7 Merge pull request #30159 from frappe/mergify/bp/version-13-hotfix/pr-29696
fix: do not reset asset_category (backport #29696)
2022-03-10 14:19:55 +05:30
Himanshu
5081438748 fix: do not reset asset_category (#29696)
(cherry picked from commit 5193a63781)
2022-03-10 08:15:21 +00:00
mergify[bot]
6c21642f32 fix(psoa): no such element: dict object['account'] (#30156) 2022-03-10 13:36:52 +05:30
Saqib Ansari
92a59fabe5 chore: imports (#30157)
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-10 13:26:11 +05:30
Saqib Ansari
903e5d03a7 Merge pull request #30151 from frappe/mergify/bp/version-13-hotfix/pr-30145
fix(psoa): add company filter to account (backport #30145)
2022-03-10 13:16:44 +05:30
Saqib Ansari
cef8820369 fix(psoa): add company filter to account
(cherry picked from commit fc42041f8f)
2022-03-10 05:25:24 +00:00
mergify[bot]
31ba09b3d0 fix: program enrollment button labels (backport #30148) (#30149)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-10 10:07:47 +05:30
Marica
c9d0692fb4 Merge pull request #30141 from frappe/mergify/bp/version-13-hotfix/pr-30091
feat: Include child item group products in Item Group Page & cleanup (backport #30091)
2022-03-09 18:11:35 +05:30
mergify[bot]
e3387a06b6 fix: customer credit limit validation on update (#30126)
(cherry picked from commit a0dc79332d)

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-03-09 17:49:34 +05:30
mergify[bot]
eb90d50946 fix: ignore non-unique swift numbers while migrating (#30139)
(cherry picked from commit 4d8798b0ea)

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-03-09 17:49:11 +05:30
mergify[bot]
63e7d8c651 perf(asset): fetch only distinct depreciable assets (#30138)
(cherry picked from commit 66f20209f6)

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-03-09 17:48:49 +05:30
Marica
458f264575 fix: Remove extra line 2022-03-09 17:35:01 +05:30
Marica
6241f9b9aa Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30091 2022-03-09 17:33:37 +05:30
Marica
db38c36bba Merge pull request #30144 from frappe/mergify/bp/version-13-hotfix/pr-30000
fix(translation) - correction for assets translation (backport #30000)
2022-03-09 17:33:13 +05:30
mergify[bot]
d68cf1885f test: Added test for monthly attendance report (backport #29989) (#30143)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: Chillar Anand <anand21nanda@gmail.com>
2022-03-09 17:32:29 +05:30
Marica
4b4b104843 fix: Merge conflicts 2022-03-09 17:29:38 +05:30
hrzzz
f721a2929f fix(translation) - correction for translation
(cherry picked from commit 16de29a3cb)

# Conflicts:
#	erpnext/assets/doctype/asset/asset_dashboard.py
2022-03-09 11:53:10 +00:00
mergify[bot]
51f7ca856b revert: BU Schlüssel (a21f76f) (#30142)
(cherry picked from commit 363ed9ccba)

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2022-03-09 16:51:58 +05:30
marination
ca41263736 fix: Linter
(cherry picked from commit 7b37a74023)
2022-03-09 11:16:56 +00:00
marination
c8026eb9df test: Test include_descendants in Item Group Product Listing
- Also made include_descendants field's visibility dependant on show_in_website

(cherry picked from commit 3507cf5985)
2022-03-09 11:16:55 +00:00
marination
7f234c7e34 feat: Include child item group products in Item Group Page & cleanup
- Added 'Include descendants' checkbox, which will pull child item group products too
- Build item group filters in query engine file
- Include logic in filter engine
- Clean up Website section of Item Group page (UX)
- Add util to fetch child item groups including self

(cherry picked from commit b2755f6fdd)
2022-03-09 11:16:55 +00:00
Deepesh Garg
b503b83960 Merge pull request #30137 from frappe/mergify/bp/version-13-hotfix/pr-30136
fix: Remove tax invoice no field (backport #30136)
2022-03-09 16:42:01 +05:30
Deepesh Garg
8d7418171b fix: Update timestamp
(cherry picked from commit 9b8258479c)
2022-03-09 10:51:05 +00:00
Deepesh Garg
856534c8a0 fix: Remove tax invoice no field
(cherry picked from commit 8a2fe7a2e3)
2022-03-09 10:51:05 +00:00
Ankush Menat
76376d7ad7 chore: bump version 2022-03-09 15:41:58 +05:30
Deepesh Garg
5c7b2e2616 Merge pull request #30133 from frappe/mergify/bp/version-13-hotfix/pr-30096
fix(ux): Improve label for better understanding (backport #30096)
2022-03-09 15:40:26 +05:30
mergify[bot]
a12a3bd441 fix: dont fetch entire barcode table in get_item_details (#30131) (#30135)
(cherry picked from commit 64905188c4)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-09 15:38:38 +05:30
mergify[bot]
58f41722b6 fix: dont fetch entire barcode table in get_item_details (#30131) (#30134)
(cherry picked from commit 64905188c4)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-09 15:38:26 +05:30
Deepesh Garg
4a5ea55965 fix(ux): Improve label for better understanding
(cherry picked from commit f3a95d3c27)
2022-03-09 10:05:11 +00:00
Deepesh Garg
ab6cdbde14 Merge pull request #29949 from frappe/mergify/bp/version-13-hotfix/pr-29865
feat: Bank Reconciliation for loan documents (backport #29865)
2022-03-09 15:23:38 +05:30
Mohamed-D-Ouf
3d8eaa5392 fix: add filters in default_discount_account (#30095) 2022-03-09 11:25:36 +05:30
Ankush Menat
ba21e2e08d Merge pull request #30123 from frappe/mergify/bp/version-13-hotfix/pr-30080
fix: Item-wise sales history report (backport #30080)
2022-03-09 11:16:34 +05:30
Ankush Menat
b3fd6652c0 Merge pull request #30122 from frappe/mergify/bp/version-13-hotfix/pr-30102
fix: Ambigous column in picklist query (backport #30102)
2022-03-09 11:15:59 +05:30
Deepesh Garg
8fc105a186 fix: Item-wise sales history report
(cherry picked from commit e2c144e9e3)
2022-03-09 04:48:32 +00:00
Deepesh Garg
95792d8f32 fix: Remove unintentional changes
(cherry picked from commit d9d4c2ce79)
2022-03-09 04:48:07 +00:00
Deepesh Garg
b9ccbd50b4 fix: Ambigous column in picklist query
(cherry picked from commit 517fbf1d1f)
2022-03-09 04:48:06 +00:00
Deepesh Garg
3aace2f420 fix: Reload doctypes 2022-03-09 10:11:01 +05:30
mergify[bot]
07b8dd0300 fix: leave allocation records query (backport #30118) (#30120)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-08 23:48:44 +05:30
mergify[bot]
e8f17e582b test: Fix flaky tests (backport #30107) (#30119)
Co-authored-by: Chillar Anand <anand21nanda@gmail.com>
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-08 23:25:59 +05:30
Deepesh Garg
6a4c0234f5 Merge pull request #29918 from frappe/mergify/bp/version-13-hotfix/pr-29740
fix: Multiple fixes in Gross Profit report (backport #29740)
2022-03-08 23:01:40 +05:30
Deepesh Garg
ac7f1820c5 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29740 2022-03-08 21:41:53 +05:30
mergify[bot]
e5c82d018f fix: Get MRs that are yet to be received but fully ordered in Report Requested Items to Order and Receive (backport #29987) (#30083)
* fix: Get MRs that are yet to be received but fully ordered in Report

- Remove incorrect query clause that only check if ordered qty < 100
- MR should be visible in report until fully received (cycle complete)

(cherry picked from commit d3b0ca30c6)

* refactor: Convert to QB, added test file, removed white space

- Converted mysql raw query to qb
- Test file for Report Requested Items to Order and Receive
- Removed white space and edited copyright year

(cherry picked from commit e6952cb7f9)

* fix: Sider and Linter

(cherry picked from commit ac425722e2)

* fix: linter (imports alphabetical)

(cherry picked from commit 54b3676f35)

Co-authored-by: marination <maricadsouza221197@gmail.com>
2022-03-08 18:00:19 +05:30
Deepesh Garg
dc8fd6d0ff Merge pull request #30104 from frappe/mergify/bp/version-13-hotfix/pr-29940
fix: Item discounts for quotation and other docs (backport #29940)
2022-03-08 14:58:43 +05:30
Rohit Waghchaure
ab0dcd7146 Merge branch 'version-13-pre-release' into version-13 2022-03-08 14:57:18 +05:30
Rohit Waghchaure
a7776f85ef bumped to version 13.22.0 2022-03-08 15:17:18 +05:50
Deepesh Garg
7de6f0a9c3 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29865 2022-03-08 14:02:26 +05:30
Deepesh Garg
07db36b23d Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29940 2022-03-08 14:02:00 +05:30
Deepesh Garg
c46e76c543 Merge pull request #30109 from deepeshgarg007/change_log_v13_22
chore: change log for v13.22.0
2022-03-08 13:06:29 +05:30
Deepesh Garg
9538c94b69 chore: change log for v13.22.0 2022-03-08 12:48:27 +05:30
Saqib Ansari
86afbf98a3 Merge pull request #30108 from frappe/mergify/bp/version-13-pre-release/pr-30097
fix(pos): multiple pos round off cases (backport #30097)
2022-03-08 12:40:16 +05:30
Saqib Ansari
150d1e6e60 fix(pos): multiple pos round off cases
(cherry picked from commit 17445c7e04)
2022-03-08 06:29:56 +00:00
Saqib Ansari
f7f114789d Merge pull request #30106 from frappe/mergify/bp/version-13-hotfix/pr-30097
fix(pos): multiple pos round off cases (backport #30097)
2022-03-08 11:59:02 +05:30
Saqib Ansari
31f20cf144 fix(pos): multiple pos round off cases
(cherry picked from commit 17445c7e04)
2022-03-08 05:55:08 +00:00
Deepesh Garg
ba4d496404 fix: Resolve conflicts 2022-03-08 09:55:36 +05:30
Türker Tunalı
456ebc32f0 fix: translate error message titles 2022-03-08 09:54:33 +05:30
Deepesh Garg
9af14ec0d0 fix: Test cases with discount
(cherry picked from commit d95f8934aa)
2022-03-08 03:39:33 +00:00
Ankush Menat
dfbd0ebf91 fix: apply margin on duplicated doc too
(cherry picked from commit bbc4710fa3)
2022-03-08 03:39:33 +00:00
Deepesh Garg
fd3adbb6fe fix: Item discounts for quotation
(cherry picked from commit 3a547cb0d9)

# Conflicts:
#	erpnext/selling/doctype/quotation/quotation.js
2022-03-08 03:39:33 +00:00
mergify[bot]
a8ae86e23f fix: wrong payment days in salary slip for employees joining/leaving during mid payroll dates (backport #29082) (#30099)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: Dany Robert <danyrt@wahni.com>
2022-03-07 20:29:52 +05:30
Deepesh Garg
2b512617fa Merge pull request #30098 from deepeshgarg007/pre_release
chore: Merge branch version-13-hotfix into version-13-pre-release
2022-03-07 19:12:42 +05:30
Deepesh Garg
7452b8572e Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into pre_release 2022-03-07 18:07:42 +05:30
Saqib Ansari
0ed7249484 Merge pull request #30089 from frappe/version-13-pre-release
Merge 'version-13-pre-release' into 'version-13'
2022-03-07 12:16:25 +05:30
Saqib Ansari
25dce9da67 chore: bumped to version 13.21.1 2022-03-07 12:15:42 +05:30
Saqib Ansari
db26928b28 Merge pull request #30086 from frappe/mergify/bp/version-13-hotfix/pr-30084
fix(e-invoicing): remove batch no from e-invoices (backport #30084)
2022-03-07 11:56:58 +05:30
Saqib Ansari
5a5d9b42c0 Merge pull request #30087 from frappe/mergify/bp/version-13-pre-release/pr-30084
fix(e-invoicing): remove batch no from e-invoices (backport #30084)
2022-03-07 11:56:40 +05:30
Saqib Ansari
052ad1fb47 fix(e-invoicing): remove batch no from e-invoices
(cherry picked from commit 031a0dd703)
2022-03-07 06:25:00 +00:00
Saqib Ansari
cf2580083c fix(e-invoicing): remove batch no from e-invoices
(cherry picked from commit 031a0dd703)
2022-03-07 06:24:21 +00:00
Saqib Ansari
58501c7e0e Merge pull request #30085 from frappe/mergify/bp/version-13-pre-release/pr-29991
fix(pos): minor fixes (backport #29991)
2022-03-07 11:53:14 +05:30
Saqib Ansari
3525f0e585 fix: merge conflicts 2022-03-07 11:52:16 +05:30
Saqib Ansari
25c434fb02 fix(pos): coupon code is applied even if ignore pricing rule is check
(cherry picked from commit 81514516f3)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2022-03-07 06:20:52 +00:00
Saqib Ansari
da4e43b8bc fix(pos): mode of payment disappears after save
(cherry picked from commit 69c34cd7ae)
2022-03-07 06:20:50 +00:00
Saqib Ansari
8af7a9918b Merge pull request #30079 from frappe/mergify/bp/version-13-hotfix/pr-29939
feat: update ordered qty for packed items (backport #29939)
2022-03-06 13:45:31 +05:30
Saqib Ansari
1e418f9ecc fix: merge conflicts 2022-03-06 13:13:50 +05:30
Saqib Ansari
447692d770 chore: remove unintentional search index
(cherry picked from commit 2f1709dfef)
2022-03-06 07:40:51 +00:00
Saqib Ansari
1c0d0e1ac7 chore: remove unintentional search index
(cherry picked from commit 1e139cf9a1)
2022-03-06 07:40:51 +00:00
Saqib Ansari
14f31ac2fa test: po updates packed item's ordered_qty
(cherry picked from commit 8e3f1e306d)

# Conflicts:
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2022-03-06 07:40:51 +00:00
Saqib Ansari
131158a24c feat: update ordered qty for packed items
(cherry picked from commit 8005fee656)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.js
2022-03-06 07:40:50 +00:00
Ankush Menat
a8137bda67 fix: dont hardcode precision in routing
Co-Authored-By: Suraj Shetty <surajshetty3416@gmail.com>
(cherry picked from commit 9ef35ef773)
2022-03-02 20:22:02 +05:30
Ankush Menat
d34f5051d7 fix: remove dead dashboard links
(cherry picked from commit f8ac4c082a)
2022-03-02 20:22:02 +05:30
Ankush Menat
5bad8d0e10 fix: dont hardcode precision in routing
Co-Authored-By: Suraj Shetty <surajshetty3416@gmail.com>
(cherry picked from commit 9ef35ef773)
2022-03-02 19:34:01 +05:30
Ankush Menat
478e1eb8db fix: remove dead dashboard links
(cherry picked from commit f8ac4c082a)
2022-03-02 19:34:01 +05:30
mergify[bot]
8ef1f679f7 chore: get stock reco qty from SR instead of SLE (#30059) (#30060)
[skip ci]

(cherry picked from commit 55a966ec41)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-02 14:50:54 +05:30
mergify[bot]
d1cc291c80 fix(Timesheet): fetch exchange rate only if currency is set (backport #30057) (#30058)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-03-02 14:19:11 +05:30
Ankush Menat
95f30ada7b fix: resolve conflicts 2022-03-02 14:08:57 +05:30
Ankush Menat
870799c4e2 fix: repost items with repeating item-warehouses
(cherry picked from commit 3638fbf06b)
2022-03-02 14:08:57 +05:30
Ankush Menat
a1c3e9aef9 revert "fix: FIFO valuation in case of multi-item entries"
This reverts commit b8ee193d1a.

This is huge performance regression for large docs.

(cherry picked from commit 701878f60b)
2022-03-02 14:08:57 +05:30
Ankush Menat
9fc0da1688 test: repack FIFO rates
(cherry picked from commit 2f71c5bcca)
2022-03-02 14:08:57 +05:30
Ankush Menat
bb58c49aef fix: FIFO valuation in case of multi-item entries
(cherry picked from commit ccd2ce56b1)
2022-03-02 14:08:57 +05:30
Ankush Menat
a84e7c633e test: FIFO transfer for multi-batch transaction
(cherry picked from commit b3b7cdfb49)

# Conflicts:
#	erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
2022-03-02 14:08:57 +05:30
Ankush Menat
5be64c2b7f docs: explain make_sl_entries arguments
(cherry picked from commit eb8495a401)
2022-03-02 13:30:20 +05:30
Ankush Menat
5ebfb65b42 fix: ignore serial no during landed cost voucher
(cherry picked from commit efc4b943f8)
2022-03-02 13:30:20 +05:30
Ankush Menat
cdafed1092 test: fix flaky bin value test
(cherry picked from commit 5d85b35f41)
2022-03-02 12:35:26 +05:30
Deepesh Garg
ab69cb7b76 Merge pull request #30014 from frappe/mergify/bp/version-13-hotfix/pr-29978
fix: Commission not applied while making Sales Order from Quotation (backport #29978)
2022-03-01 18:19:10 +05:30
Deepesh Garg
69d3678d21 Merge pull request #30025 from deepeshgarg007/payment_entry_exchange_rate
fix: Exchange rate not getting set in payment entry
2022-03-01 18:18:42 +05:30
mergify[bot]
28a7c79f3c fix: dont validate empty category (#30038)
(cherry picked from commit 65bb727030)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-03-01 17:57:10 +05:30
mergify[bot]
c7539ae416 fix(test): flaky test_point_of_sale (#30035)
(cherry picked from commit 47fe87a72f)

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-03-01 17:51:40 +05:30
Saqib Ansari
3eee7412c5 Merge pull request #30033 from frappe/mergify/bp/version-13-hotfix/pr-30032
fix: debit credit difference case with rounding adjustment (backport #30032)
2022-03-01 15:33:10 +05:30
rohitwaghchaure
1e5b9e747f Merge pull request #30023 from frappe/mergify/bp/version-13-hotfix/pr-30015
refactor: removed validation to check zero qty (backport #30015)
2022-03-01 14:44:31 +05:30
Saqib Ansari
d93a41e0d0 fix: debit credit difference case with rounding adjustment
(cherry picked from commit ad2c64f3ff)
2022-03-01 08:43:25 +00:00
Deepesh Garg
40c0f79291 Merge pull request #30027 from frappe/mergify/bp/version-13-hotfix/pr-30026
fix: Deferred revenue booking (backport #30026)
2022-03-01 13:26:28 +05:30
Ankush Menat
5f4965022f fix: track changes on warehouse
(cherry picked from commit d22a1d440c)
2022-03-01 13:11:49 +05:30
Ankush Menat
45ba50036f fix: track changes on warehouse
(cherry picked from commit d22a1d440c)
2022-03-01 13:11:35 +05:30
Deepesh Garg
5f9b2cc1bf fix: Deferred revenue booking
(cherry picked from commit 366120ffee)
2022-03-01 07:12:02 +00:00
Deepesh Garg
86e6bdf2c9 fix: Exchange rate not getting set in payment entry 2022-02-28 21:07:08 +05:30
Deepesh Garg
fb05230d51 Merge pull request #30024 from frappe/mergify/bp/version-13-hotfix/pr-29977
fix: Total taxes and charges in payment entry for multi-currency payments (backport #29977)
2022-02-28 19:36:21 +05:30
Ankush Menat
e89d60147e test: remove transaction commits from buying module
(cherry picked from commit 829c453cb6)
2022-02-28 19:28:36 +05:30
Deepesh Garg
de978ca3d7 test: Add test case for payment entry taxes
(cherry picked from commit 19fb7ead9f)
2022-02-28 13:40:22 +00:00
Deepesh Garg
6ed01fedd4 fix: Total taxes and charges in payment entry for multicurrency payments
(cherry picked from commit b1a46c80d5)
2022-02-28 13:40:22 +00:00
Deepesh Garg
ca6f196bad fix: Remove conflicts 2022-02-28 18:15:29 +05:30
Rohit Waghchaure
b98d3da8e3 fix: removed validation to check zero qty
(cherry picked from commit 39d3f20d9b)
2022-02-28 12:25:20 +00:00
Ankush Menat
aa684e6f3d fix: dont fetch draft/cancelled BOMs
(cherry picked from commit 1d1203d5ec)
2022-02-28 15:15:36 +05:30
Deepesh Garg
ebf70a4b5d test: add correct test case
(cherry picked from commit 4e9a9f35a6)
2022-02-28 07:35:04 +00:00
Deepesh Garg
bce4ca9390 fix: Commission not applied while making Sales Order from Quotation
(cherry picked from commit 0373914704)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
2022-02-28 07:35:04 +00:00
Deepesh Garg
9c0f8fe7fd Merge pull request #30009 from frappe/mergify/bp/version-13-hotfix/pr-29928
fix: Account filter in PSOA (backport #29928)
2022-02-28 12:57:04 +05:30
Marica
5aa42c736f Merge pull request #29994 from marination/fix-user-unique-cart-hotfix
fix(e-commerce): Unique Shopping Cart Per Logged In User
2022-02-28 12:37:39 +05:30
Marica
a531792ea5 Merge branch 'version-13-hotfix' into fix-user-unique-cart-hotfix 2022-02-28 12:13:57 +05:30
Deepesh Garg
c9da4e1059 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29865 2022-02-28 11:43:01 +05:30
Deepesh Garg
e509ce5438 fix: Account filter in PSOA
(cherry picked from commit 70b960e650)
2022-02-28 06:12:42 +00:00
Saqib Ansari
f21951fa33 Merge pull request #29992 from frappe/mergify/bp/version-13-hotfix/pr-29991
fix(pos): minor fixes (backport #29991)
2022-02-28 11:34:27 +05:30
mergify[bot]
5d69a182f6 fix(ux): make "allow zero valuation rate" readonly if "s_warehouse" is set (#29681) (#30002)
* chore: make allow zero valuation rate readonly if s_warehouse is set

* fix: setting the checkbox to zero whenever the source warehouse is set

* fix: remove allow_on_submit and refresh trigger

Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 77ffcd3aed)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-02-26 12:12:10 +05:30
Sagar Sharma
067ede76ea fix: validate Work Order qty against Production Plan (#29721)
* fix: validate Work Order qty against Production Plan

* chore: err msg when max_qty is 0

* test: add test for overproduction

* fix: CI
2022-02-26 11:13:00 +05:30
mergify[bot]
afade046fb fix: org chart connectors not rendered when Employee Naming is set to Full Name (backport #29997) (#29998)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-25 23:07:04 +05:30
Devin Slauenwhite
03945aae37 fix: get cart items for logged in user. 2022-02-25 16:43:21 +05:30
Devin Slauenwhite
78c2a369c4 fix: get cart for logged in user. 2022-02-25 16:43:12 +05:30
Devin Slauenwhite
a067aabb3c test: many users linked to customer shopping cart 2022-02-25 16:43:04 +05:30
Marica
c099833560 Merge pull request #29970 from frappe/mergify/bp/version-13-hotfix/pr-29913
fix: Block merging items if both have product bundles (backport #29913)
2022-02-25 16:10:09 +05:30
Saqib Ansari
6bb1dccddd fix: merge conflicts 2022-02-25 15:45:52 +05:30
Saqib Ansari
f2264fa58d fix(pos): coupon code is applied even if ignore pricing rule is check
(cherry picked from commit 81514516f3)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2022-02-25 10:06:21 +00:00
Saqib Ansari
dcef0de906 fix(pos): mode of payment disappears after save
(cherry picked from commit 69c34cd7ae)
2022-02-25 10:06:21 +00:00
Deepesh Garg
5ec6a96882 Merge pull request #29960 from frappe/mergify/bp/version-13-hotfix/pr-29879
fix: Validate party account with company (backport #29879)
2022-02-24 00:00:30 +05:30
Deepesh Garg
9464b3c3e6 fix: Make abbreviation limit to 10 2022-02-23 20:43:48 +05:30
Ankush Menat
82ed96859a Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29913 2022-02-23 17:21:25 +05:30
mergify[bot]
95bf7acff8 test: fix flaky stateful tests (#29749) (#29969)
Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-Authored-By: Marica <maricadsouza221197@gmail.com>
2022-02-23 17:07:13 +05:30
marination
34e0b85d6a fix: Check if both old and new items have bundles before merging
- If only one has bundle against it, they can be merged

(cherry picked from commit a33f04ea41)
2022-02-23 11:30:04 +00:00
marination
e0ed91433b test: Item Merging with Product Bundles
(cherry picked from commit 530f9f7029)
2022-02-23 11:30:04 +00:00
marination
5d1b7ec61d fix: Block merging items if both have product bundles
(cherry picked from commit 28cc2dbb72)
2022-02-23 11:30:04 +00:00
mergify[bot]
cdcd60a1ea chore: Rollback after each test, due to premature commit via remove_user_permission (backport #29964) (#29967)
* chore: Rollback after each test, due to premature commit via `remove_user_permission`

- `remove_user_permission` in `test_warehouse_user` calls delete_doc that enqueues dynamic link deletion
- Execution of background job eventually commits
- While in the test suite it runs sequentially in the same thread and commits whatever was done until then
- Which is why the rollback in `tearDownClass` is quite useless here
- This premature commit causes many illegal transactions caught by `assertRaises` to be committed in the db
- This creates faulty/dirty ledgers and breaks reports, as outiside the test suite this shouldn't/wouldn't happen
- Rollback after each test, and for `test_warehouse_user` in particular, manually cancel transaction

(cherry picked from commit bf8743713d)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py

* test: Make Variant if absent in `test_variant_work_order`, keep test atomic

(cherry picked from commit 5ff3705872)

* fix: Merge conflicts

Co-authored-by: marination <maricadsouza221197@gmail.com>
2022-02-23 16:09:37 +05:30
Ankush Menat
37ba3666ac fix: reset stock value if no qty 2022-02-23 14:03:07 +05:30
Ankush Menat
6d354edb6b refactor: code duplication for fallback rates 2022-02-23 14:03:07 +05:30
Ankush Menat
982476d684 chore: batch flag and consumption rate in invariant report 2022-02-23 14:03:07 +05:30
Deepesh Garg
da1713ad00 fix: Remove unintended changes
(cherry picked from commit a61790c00f)
2022-02-23 05:52:53 +00:00
Deepesh Garg
e82f55ce9e fix: Validate party account with company
(cherry picked from commit 5a2b571aa9)
2022-02-23 05:52:52 +00:00
Deepesh Garg
c040d7df8a Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29740 2022-02-23 11:10:54 +05:30
Deepesh Garg
d1049b0d00 fix: Resolve conflicts 2022-02-23 11:10:07 +05:30
Suraj Shetty
1fbf2b511f Merge pull request #29958 from frappe/mergify/bp/version-13-hotfix/pr-29956 2022-02-23 11:01:38 +05:30
Suraj Shetty
cebe8888af fix: Email translations
(cherry picked from commit aaa84a21ba)
2022-02-23 05:31:15 +00:00
Saqib Ansari
1689409438 Merge pull request #29714 from GangaManoj/fetch-valuation-rate
fix: Fetch valuation rate for stock items consumed during asset repair
2022-02-23 09:24:27 +05:30
Deepesh Garg
259a091a37 fix: Remove print statements
(cherry picked from commit a4c6cb9f12)
2022-02-22 15:14:01 +00:00
Deepesh Garg
247325e4f5 fix: Update bank reconciliation statement
(cherry picked from commit 0b5e618e3a)
2022-02-22 15:14:01 +00:00
Deepesh Garg
e998521ca6 fix: Update queries in Bank Reconciliation Tool
(cherry picked from commit 295cbb0ff2)
2022-02-22 15:14:00 +00:00
Deepesh Garg
c2129b762a fix: Add patch for account fields
(cherry picked from commit a0bdcbd0cd)

# Conflicts:
#	erpnext/patches.txt
2022-02-22 15:13:59 +00:00
Deepesh Garg
93033fb87f feat: Bank Reconciliation for loan documents
(cherry picked from commit 555b1335f6)
2022-02-22 15:13:58 +00:00
Deepesh Garg
ee84985dc7 Merge pull request #29947 from frappe/mergify/bp/version-13-hotfix/pr-29942
fix: Taxjar minor fixes (backport #29942)
2022-02-22 19:39:39 +05:30
Subin Tom
46ed9a059c fix: Taxjar minor fixes
(cherry picked from commit 1682a26fe6)
2022-02-22 14:08:52 +00:00
Saqib Ansari
945a685a34 Merge pull request #29937 from frappe/mergify/bp/version-13-hotfix/pr-29900
fix(asset): no. of depr booked cannot be equal to total no. of depr (backport #29900)
2022-02-22 18:45:01 +05:30
Saqib Ansari
f4d987c21e Merge branch 'version-13-hotfix' into fetch-valuation-rate 2022-02-22 18:44:46 +05:30
mergify[bot]
807997c6ec fix: remove customer field value when MR is not customer provided (#29938) (#29941)
(cherry picked from commit 7f55226a58)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-22 18:20:59 +05:30
Saqib Ansari
b6d42ff571 test: number_of_depr_booked = total_number_of_depr
(cherry picked from commit 780694f6e2)
2022-02-22 08:54:16 +00:00
Saqib Ansari
2e6a076f90 fix(asset): no. of depreciation booked cannot be equal to total no. of depreciations
(cherry picked from commit c5808543c8)
2022-02-22 08:54:16 +00:00
Saqib Ansari
7157a86ea0 Merge branch 'version-13-hotfix' into fetch-valuation-rate 2022-02-22 14:23:29 +05:30
mergify[bot]
6539d32ca9 fix: Fetch conversion factor even if it already existed in row, on item change (#29917) (#29935)
* fix: Fetch conversion factor even if it already existed in row, on item change

* fix: Retain manually changed conversion factor

- If item code changes, reset conversion factor on client side
- Keep API behavious consistent, if conversion factor is sent, same must come back
- API should not ideally reset values in most cases

(cherry picked from commit 235fc127b3)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2022-02-22 13:01:31 +05:30
mergify[bot]
0f101b5209 fix(Salary Slip): TypeError while clearing any amount field in components (backport #29931) (#29932)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-22 11:56:56 +05:30
mergify[bot]
233a75a438 fix(LMS): program enrollment does not give any feedback (backport #29922) (#29924)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-21 23:10:20 +05:30
Rohit Waghchaure
f87ba6ac0a Merge branch 'version-13-pre-release' into version-13 2022-02-21 20:29:03 +05:30
Rohit Waghchaure
506e1221b8 bumped to version 13.21.0 2022-02-21 20:49:03 +05:50
rohitwaghchaure
a6a7504d6c Merge pull request #29920 from rohitwaghchaure/change-log-for-v13-21
chore: change log for v13.21.0
2022-02-21 20:21:27 +05:30
Rohit Waghchaure
b05d2201eb chore: change log for v13.21.0 2022-02-21 20:18:04 +05:30
mergify[bot]
6b87d38a80 fix: JobCard TimeLog to_date (#29872) (#29919)
(cherry picked from commit e4c4dc402e)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2022-02-21 20:17:05 +05:30
Deepesh Garg
a63373ce98 fix: Gross profit for credit notes
(cherry picked from commit 973f6b1bbd)
2022-02-21 13:59:15 +00:00
Deepesh Garg
9a0b83027a fix: Remove unused param
(cherry picked from commit 07bcbc6c7e)
2022-02-21 13:59:15 +00:00
Deepesh Garg
b0a9ff0b04 fix: Update columns in new format
(cherry picked from commit 2172ab2d37)
2022-02-21 13:59:15 +00:00
Deepesh Garg
ab4a96dadd fix: Multiple fixes in Gross Profit report
(cherry picked from commit da73685f71)
2022-02-21 13:59:15 +00:00
Saqib Ansari
f107a6f3f2 Merge branch 'version-13-hotfix' into fetch-valuation-rate 2022-02-21 17:56:11 +05:30
Ankush Menat
9adbaaea4a fix: round off increments in numeric item variant
(cherry picked from commit 00e8565868)
2022-02-21 17:44:41 +05:30
Deepesh Garg
b10d167c98 Merge pull request #29910 from frappe/mergify/bp/version-13-pre-release/pr-29907
fix: Total Credit amount in TDS Payable monthly report (backport #29907)
2022-02-21 15:40:53 +05:30
Deepesh Garg
e4281734fe Merge pull request #29909 from frappe/mergify/bp/version-13-hotfix/pr-29907
fix: Total Credit amount in TDS Payable monthly report (backport #29907)
2022-02-21 15:40:41 +05:30
Deepesh Garg
a5fbf89285 fix: Total Credit amount in TDS Payable monthly report
(cherry picked from commit a82cf7214e)
2022-02-21 10:05:49 +00:00
Deepesh Garg
a6fba24ab1 fix: Total Credit amount in TDS Payable monthly report
(cherry picked from commit a82cf7214e)
2022-02-21 10:05:23 +00:00
Saqib Ansari
1b0ee018b5 Merge branch 'version-13-hotfix' into fetch-valuation-rate 2022-02-21 15:11:26 +05:30
Deepesh Garg
15849c5cac Merge pull request #29906 from frappe/mergify/bp/version-13-pre-release/pr-29887
fix: Ledger entries on LIA for term loans (backport #29887)
2022-02-21 14:54:18 +05:30
Deepesh Garg
e70933eea2 Merge pull request #29905 from frappe/mergify/bp/version-13-hotfix/pr-29887
fix: Ledger entries on LIA for term loans (backport #29887)
2022-02-21 14:54:05 +05:30
Marica
28abe13e0b Merge pull request #29908 from frappe/mergify/bp/version-13-pre-release/pr-29904
chore: Show 'Produced Qty' field in Sales Order Item (backport #29903) (backport #29904)
2022-02-21 14:42:22 +05:30
mergify[bot]
251288994c chore: Show 'Produced Qty' field in Sales Order Item (#29903) (#29904)
(cherry picked from commit e952cce17d)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 14c5be7c27)
2022-02-21 09:06:01 +00:00
Saqib Ansari
bc28755978 fix: sider 2022-02-21 14:26:21 +05:30
Deepesh Garg
a015fcc921 fix: Ledger entries on LIA for term loans
(cherry picked from commit 1aa12fb3f1)
2022-02-21 08:55:36 +00:00
Deepesh Garg
130f039549 fix: Ledger entries on LIA for term loans
(cherry picked from commit 1aa12fb3f1)
2022-02-21 08:54:58 +00:00
mergify[bot]
14c5be7c27 chore: Show 'Produced Qty' field in Sales Order Item (#29903) (#29904)
(cherry picked from commit e952cce17d)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2022-02-21 14:23:52 +05:30
Saqib Ansari
2aba6c38b3 fix: cannot edit valutaion_rate in asset repair 2022-02-21 14:01:12 +05:30
Saqib Ansari
57d5a027fb fix: remove redundant method 2022-02-21 13:58:01 +05:30
Saqib Ansari
c34fcf3083 Merge pull request #29899 from frappe/mergify/bp/version-13-pre-release/pr-29896
fix(pos): removal of coupon code (backport #29896)
2022-02-21 12:02:49 +05:30
Saqib Ansari
2c84b1dfa6 Merge pull request #29898 from frappe/mergify/bp/version-13-hotfix/pr-29896
fix(pos): removal of coupon code (backport #29896)
2022-02-21 11:52:31 +05:30
Saqib Ansari
9b241cb6f1 fix(pos): removal of coupon code
(cherry picked from commit fa38c291bd)
2022-02-21 06:19:46 +00:00
Saqib Ansari
8d24cb44a8 fix(pos): removal of coupon code
(cherry picked from commit fa38c291bd)
2022-02-21 06:19:24 +00:00
rohitwaghchaure
85b7bc8d6b Merge pull request #29892 from frappe/mergify/bp/version-13-hotfix/pr-29771
fix: Error in consolidated financial statements (backport #29771)
2022-02-20 19:54:44 +05:30
Deepesh Garg
232ba3580b fix: Linting issues
(cherry picked from commit fec40aac7a)
2022-02-20 13:48:45 +00:00
Deepesh Garg
b99a95b280 fix: Remove commented out code
(cherry picked from commit 42cdd6d237)
2022-02-20 13:48:45 +00:00
Deepesh Garg
02b7541fb9 fix: Error in consolidated financial statements
(cherry picked from commit ae613008be)
2022-02-20 13:48:45 +00:00
Deepesh Garg
ce2a5c97eb Merge pull request #29888 from frappe/mergify/bp/version-13-hotfix/pr-29869
fix: GSTIN filter for GSTR-1 report (backport #29869)
2022-02-20 17:25:10 +05:30
Deepesh Garg
6084aeb7d1 chore: remove console statements
(cherry picked from commit d188fcc066)
2022-02-19 14:12:43 +00:00
Deepesh Garg
3a98cc351c Update gstr_1.js
(cherry picked from commit a28ec89507)
2022-02-19 14:12:43 +00:00
Deepesh Garg
41b82145ea fix: GSTIN filter for GSTR-1 report
(cherry picked from commit 87b074ac09)
2022-02-19 14:12:42 +00:00
Marica
920a99ee76 Merge pull request #29877 from frappe/mergify/bp/version-13-pre-release/pr-29816
fix: Stock Ageing Transfer Bucket logic for Repack Entry with split batch rows (backport #29816)
2022-02-18 20:02:17 +05:30
Marica
8dc6468b6a Merge pull request #29878 from frappe/mergify/bp/version-13-pre-release/pr-29825
fix: Update SO via Work Order made from MR (attached to SO) (backport #29803) (backport #29825)
2022-02-18 20:01:18 +05:30
Marica
52aedcf8c4 Merge pull request #29876 from frappe/mergify/bp/version-13-hotfix/pr-29816
fix: Stock Ageing Transfer Bucket logic for Repack Entry with split batch rows (backport #29816)
2022-02-18 19:34:35 +05:30
Marica
104d3e1019 fix: Solve merge conflict in patches.txt 2022-02-18 19:27:11 +05:30
marination
6cf1049495 chore: Move patch that updates SO from WO to v13
(cherry picked from commit 94a1016623)

# Conflicts:
#	erpnext/patches.txt
2022-02-18 13:51:12 +00:00
marination
b46bd0d9d1 chore: Patch to update SO work_order_qty and Linter fix
(cherry picked from commit 0ca58d7627)

# Conflicts:
#	erpnext/patches.txt
(cherry picked from commit 98ce6a1c8d)

# Conflicts:
#	erpnext/patches.txt
2022-02-18 13:51:12 +00:00
marination
0e3398c761 test: SO > MR > WO flow
(cherry picked from commit f9d52e7346)
(cherry picked from commit 2ce07eff71)
2022-02-18 13:51:11 +00:00
marination
8dbe953b8f fix: Update SO via Work Order made from MR (attached to SO)
- Add SO Item reference in WO from MR (that was made from SO)

(cherry picked from commit 18731622c4)
(cherry picked from commit bb119c8e52)
2022-02-18 13:51:10 +00:00
marination
113222628b test: Negative Stock, over consumption & over production with split rows, balance precision
(cherry picked from commit d5be536740)
2022-02-18 13:50:36 +00:00
marination
ae7aead4d0 fix: Range Qty precision
(cherry picked from commit ed4a6c6cc6)
2022-02-18 13:50:36 +00:00
marination
4126e42ae7 fix: Precision of available qty and negative stock in transfer bucket
- Maintain only positive values in transfer bucket
- Use it to neutralize/add stock to fifo queue

(cherry picked from commit d3fbbcfed3)
2022-02-18 13:50:36 +00:00
marination
51af328a53 chore: Add transfer bucket working to .md file
(cherry picked from commit f6233e77c6)
2022-02-18 13:50:35 +00:00
marination
06758e53fd test: Stock Ageing FIFO buckets for Repack entry with same item
(cherry picked from commit ea3b7de867)
2022-02-18 13:50:35 +00:00
marination
cb4e6b9221 fix: Transfer Bucket logic for Repack Entry with split batch rows
(cherry picked from commit 799671c748)
2022-02-18 13:50:35 +00:00
Marica
f958956322 Merge pull request #29825 from frappe/mergify/bp/version-13-hotfix/pr-29803
fix: Update SO via Work Order made from MR (attached to SO) (backport #29803)
2022-02-18 19:20:12 +05:30
marination
88ca371887 test: Negative Stock, over consumption & over production with split rows, balance precision
(cherry picked from commit d5be536740)
2022-02-18 13:49:09 +00:00
marination
fde5a9e6f7 fix: Range Qty precision
(cherry picked from commit ed4a6c6cc6)
2022-02-18 13:49:09 +00:00
marination
c66b5752e2 fix: Precision of available qty and negative stock in transfer bucket
- Maintain only positive values in transfer bucket
- Use it to neutralize/add stock to fifo queue

(cherry picked from commit d3fbbcfed3)
2022-02-18 13:49:08 +00:00
marination
cab05dbefb chore: Add transfer bucket working to .md file
(cherry picked from commit f6233e77c6)
2022-02-18 13:49:08 +00:00
marination
690ae52a8e test: Stock Ageing FIFO buckets for Repack entry with same item
(cherry picked from commit ea3b7de867)
2022-02-18 13:49:08 +00:00
marination
d86e63e22d fix: Transfer Bucket logic for Repack Entry with split batch rows
(cherry picked from commit 799671c748)
2022-02-18 13:49:07 +00:00
Rohit Waghchaure
58aedaa0f7 fix: added item name in the excel sheet
(cherry picked from commit 02e77029fa)
2022-02-17 20:18:01 +05:30
Rohit Waghchaure
72dec4b28f fix: added item name in the excel sheet
(cherry picked from commit 02e77029fa)
2022-02-17 20:17:47 +05:30
Ankush Menat
90ebe6f266 fix: avoid creating bins without item-wh
Co-Authored-By:  Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com>
Co-Authored-By: Saurabh <saurabh6790@gmail.com>
(cherry picked from commit c36bd7e1a6)
2022-02-17 20:17:13 +05:30
Ankush Menat
29a1f338a4 fix: avoid creating bins without item-wh
Co-Authored-By:  Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com>
Co-Authored-By: Saurabh <saurabh6790@gmail.com>
(cherry picked from commit c36bd7e1a6)
2022-02-17 20:16:57 +05:30
Deepesh Garg
bdef164061 Merge pull request #29827 from frappe/mergify/bp/version-13-hotfix/pr-29137
feat: Payment Terms Status report (backport #29137)
2022-02-17 19:16:28 +05:30
Rucha Mahabal
ddfdb445fa chore: remove unused code and fields related to workstation from Timesheet Detail
(cherry picked from commit bef46e2b64)
2022-02-17 18:22:43 +05:30
Rucha Mahabal
98bafc5898 test: timesheet not overlapping with continuous timelogs
(cherry picked from commit 47ff968253)
2022-02-17 18:22:43 +05:30
Rucha Mahabal
91e6fea2cd fix: convert overlap raw query to frappe.qb
(cherry picked from commit 3ec9acf8f7)
2022-02-17 18:22:43 +05:30
Rucha Mahabal
6db0ea8be8 fix(Timesheet): convert time logs to datetime while checking for overlap
(cherry picked from commit e2e998fbd9)
2022-02-17 18:22:43 +05:30
Saqib Ansari
f9282d2a6b Merge pull request #29861 from frappe/mergify/bp/version-13-pre-release/pr-29859
fix: coupon code is applied even if ignore_pricing_rule is enabled (backport #29859)
2022-02-17 16:22:05 +05:30
Saqib Ansari
5db80b2b57 Merge pull request #29860 from frappe/mergify/bp/version-13-hotfix/pr-29859
fix: coupon code is applied even if ignore_pricing_rule is enabled (backport #29859)
2022-02-17 16:20:51 +05:30
Saqib Ansari
381ccd7653 fix: coupon code is applied even if ignore_pricing_rule is enabled
(cherry picked from commit 2743999785)
2022-02-17 10:42:43 +00:00
Saqib Ansari
63079f3e07 fix: coupon code is applied even if ignore_pricing_rule is enabled
(cherry picked from commit 2743999785)
2022-02-17 10:42:15 +00:00
Ankush Menat
20f7db98cb fix: production plan status should consider qty + WO status
(cherry picked from commit db93f26f20)
2022-02-17 15:09:08 +05:30
Saqib Ansari
6559c1cd01 Merge pull request #29851 from frappe/mergify/bp/version-13-pre-release/pr-29848
fix: currency in bank reconciliation tool (backport #29848)
2022-02-17 14:40:22 +05:30
Saqib Ansari
a4cd33441f Merge pull request #29850 from frappe/mergify/bp/version-13-hotfix/pr-29848
fix: currency in bank reconciliation tool (backport #29848)
2022-02-17 14:40:16 +05:30
Saqib Ansari
d898cb45dc fix: currency in bank reconciliation tool
(cherry picked from commit 60674e52b8)
2022-02-17 08:57:47 +00:00
Saqib Ansari
97505fdc82 fix: currency in bank reconciliation tool
(cherry picked from commit 60674e52b8)
2022-02-17 08:57:28 +00:00
Ankush Menat
fb0167ffc6 test: putaway rule re-application shouldn't do anything
(cherry picked from commit d9fc3f3d90)
2022-02-17 12:47:44 +05:30
Ankush Menat
90f2cfd102 fix: avoid updating items table if no change due to putaway
(cherry picked from commit b000e93744)
2022-02-17 12:47:44 +05:30
mergify[bot]
8953794cda fix: allow renaming and merging (backport #29830) (#29832)
Co-authored-by: Wolfram Schmidt <wolfram.schmidt@phamos.eu>
2022-02-16 17:53:00 +05:30
marination
94a1016623 chore: Move patch that updates SO from WO to v13 2022-02-16 17:45:41 +05:30
Marica
8727addec3 Merge pull request #29824 from frappe/mergify/bp/version-13-pre-release/pr-29823
perf: Weed out disabled variants via sql query instead of pythonic looping separately (backport #29639) (backport #29823)
2022-02-16 14:30:47 +05:30
ruthra kumar
2ca25fc9f1 test: added test for alternate currency
- Sales Order and Invoice will be submitted in USD with exchange rate
of 70 with the default company currency
- Report will display in defauly company currency

(cherry picked from commit 48f37c7659)
2022-02-16 08:46:23 +00:00
ruthra kumar
fa03246a94 test: refactor and fix failing test case
(cherry picked from commit 49fdc6c52e)
2022-02-16 08:46:23 +00:00
ruthra kumar
07c094152e refactor: create invoices list without if else
(cherry picked from commit a4b8d67323)
2022-02-16 08:46:22 +00:00
ruthra kumar
63c73e6eaf fix: default to company currency in report output
(cherry picked from commit 85ed0fb8d6)
2022-02-16 08:46:22 +00:00
ruthra kumar
2614de9ab3 refactor: currency field and code cleanup
(cherry picked from commit bc244d0740)
2022-02-16 08:46:22 +00:00
Deepesh Garg
b249478fcc fix: Copyright info
(cherry picked from commit 4284017e9d)
2022-02-16 08:46:21 +00:00
ruthra
b6910912a9 test: qty and rate changed to remove need for fractional Nos
(cherry picked from commit 4535a7a301)
2022-02-16 08:46:21 +00:00
ruthra
c6fb52a31a refactor: remove unused imports
(cherry picked from commit edd980acdc)
2022-02-16 08:46:21 +00:00
ruthra
408704e269 test: fix failing test case payment terms status
(cherry picked from commit 9f1e68801d)
2022-02-16 08:46:20 +00:00
ruthra
bc2b4411ee feat: Payment Terms Status report
- calculate status at runtime for payment terms based on invoices
 - invoices are used in FIFO method

(cherry picked from commit 1bac793083)
2022-02-16 08:46:20 +00:00
marination
98ce6a1c8d chore: Patch to update SO work_order_qty and Linter fix
(cherry picked from commit 0ca58d7627)

# Conflicts:
#	erpnext/patches.txt
2022-02-16 08:35:11 +00:00
marination
2ce07eff71 test: SO > MR > WO flow
(cherry picked from commit f9d52e7346)
2022-02-16 08:35:09 +00:00
marination
bb119c8e52 fix: Update SO via Work Order made from MR (attached to SO)
- Add SO Item reference in WO from MR (that was made from SO)

(cherry picked from commit 18731622c4)
2022-02-16 08:35:09 +00:00
marination
ad0d865ccc chore: Remove commented out code
(cherry picked from commit 29c576e144)
(cherry picked from commit de834a8362)
2022-02-16 08:33:44 +00:00
marination
abd5d3caea chore: Fix flaky test test_exact_match_with_price
- Clear cart settings in cache to avoid stale values

(cherry picked from commit 4f5a0b8941)
(cherry picked from commit b75e982286)
2022-02-16 08:33:44 +00:00
marination
2bcc77583a fix: Trim spaces from attributes (multi-variant creation) & explicit method for building cache
- Multiple Item Variants creation fails due to extra spaces in attributes from popup. Clean them before passing to server side
- Mention explicit method to build variants cache to avoid ambiguity between old method path (pre-refactor)

(cherry picked from commit a64228741d)
(cherry picked from commit ca8986028d)
2022-02-16 08:33:43 +00:00
marination
585b760144 perf: Weed out disabled variants via sql query instead of pythonic looping separately
- If the number of variants are large (almost 2lakhs), the query to get variants and attribute data takes time
- If the no.of disabled attributes is large as well, the list comprehension weeding out disabled variants takes forever
- We dont need to loop over the variants data so many times
- Avoid any `if a in list(b)` is best when the iterables have tremendous data

(cherry picked from commit 26bd3053d1)
(cherry picked from commit 251656ab52)
2022-02-16 08:33:43 +00:00
Marica
ec0ea604ac Merge pull request #29823 from frappe/mergify/bp/version-13-hotfix/pr-29639
perf: Weed out disabled variants via sql query instead of pythonic looping separately (backport #29639)
2022-02-16 14:02:54 +05:30
marination
de834a8362 chore: Remove commented out code
(cherry picked from commit 29c576e144)
2022-02-16 08:10:00 +00:00
marination
b75e982286 chore: Fix flaky test test_exact_match_with_price
- Clear cart settings in cache to avoid stale values

(cherry picked from commit 4f5a0b8941)
2022-02-16 08:10:00 +00:00
marination
ca8986028d fix: Trim spaces from attributes (multi-variant creation) & explicit method for building cache
- Multiple Item Variants creation fails due to extra spaces in attributes from popup. Clean them before passing to server side
- Mention explicit method to build variants cache to avoid ambiguity between old method path (pre-refactor)

(cherry picked from commit a64228741d)
2022-02-16 08:10:00 +00:00
marination
251656ab52 perf: Weed out disabled variants via sql query instead of pythonic looping separately
- If the number of variants are large (almost 2lakhs), the query to get variants and attribute data takes time
- If the no.of disabled attributes is large as well, the list comprehension weeding out disabled variants takes forever
- We dont need to loop over the variants data so many times
- Avoid any `if a in list(b)` is best when the iterables have tremendous data

(cherry picked from commit 26bd3053d1)
2022-02-16 08:09:59 +00:00
mergify[bot]
765ade74e6 fix: add supported currencies (#29805) (#29822)
(cherry picked from commit a26183e205)

Co-authored-by: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com>
2022-02-16 13:04:20 +05:30
mergify[bot]
dccf2a3dec test: set correct DocType (#29819) (#29820)
(cherry picked from commit 08a391fa88)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-02-16 11:52:03 +05:30
mergify[bot]
3b4e4955f7 fix: task status loop (backport #26006) (#29821)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: Jannat Patel <31363128+pateljannat@users.noreply.github.com>
2022-02-16 11:32:40 +05:30
rohitwaghchaure
c843b0b210 Merge pull request #29809 from rohitwaghchaure/merge-branch-hotfix-to-pre-release-for-13-21
chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release'
2022-02-15 17:54:17 +05:30
mergify[bot]
20ba40066a fix: dont attempt to set batch number if item doesn't have batch no (#29812) (#29814)
This causes other triggers and unnecessary changes (e.g. price list)

(cherry picked from commit f89a64db48)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-15 16:12:17 +05:30
mergify[bot]
fd5ce9ca10 fix: dont attempt to set batch number if item doesn't have batch no (#29812) (#29813)
This causes other triggers and unnecessary changes (e.g. price list)

(cherry picked from commit f89a64db48)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-15 16:12:03 +05:30
Deepesh Garg
335e80f787 Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into merge-branch-hotfix-to-pre-release-for-13-21 2022-02-15 15:56:33 +05:30
Deepesh Garg
3e2b9a2c50 Merge pull request #29810 from frappe/mergify/bp/version-13-hotfix/pr-29791
fix: Fixes in TDS payable monthly report (backport #29791)
2022-02-15 15:55:08 +05:30
Deepesh Garg
038d729462 fix: Filter out bank payment entries
(cherry picked from commit 04cbde2e52)
2022-02-15 10:10:29 +00:00
Deepesh Garg
ea3acb09be fix: Fixes in TDS payable monthly report
(cherry picked from commit 2ff6b3560e)
2022-02-15 10:10:28 +00:00
Rohit Waghchaure
01356031bb chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release' 2022-02-15 15:14:10 +05:30
Sagar Sharma
83ab63ba81 chore: warning for Amazon MWS integration deprecation (#29792)
* chore: warning for amazon mws integration deprecation

* style: spaces to tabs

[skip ci]
2022-02-15 11:37:29 +05:30
Deepesh Garg
321cbd5982 Merge pull request #29800 from frappe/mergify/bp/version-13-hotfix/pr-29789
fix: disable rounded total in opening invoice creation tool (backport #29789)
2022-02-15 11:32:14 +05:30
Saqib Ansari
5d608edc54 fix: disable rounded total in opening invoice creation tool
(cherry picked from commit 18d0a59a9d)
2022-02-15 05:09:01 +00:00
Marica
93cde7e5ff Merge pull request #29794 from frappe/mergify/bp/version-13-hotfix/pr-29705
fix: Set Pending Qty in Prod Plan after updating Work Order (backport #29705)
2022-02-14 23:39:26 +05:30
Marica
a8107f6e06 Merge pull request #29795 from frappe/mergify/bp/version-13-hotfix/pr-29788
fix: Generate Warehouse wise FIFO Queue always and later aggregate if required (backport #29788)
2022-02-14 23:38:52 +05:30
marination
e9ef1cbd3d test: Cover back to back recos from different warehouses
(cherry picked from commit f221a0d253)
2022-02-14 17:35:38 +00:00
marination
3d45ea0c1f fix: Generate Wh wise FIFO Queue and later aggregate if required
- Back to back stock recos cause incorrect qty calculation across warehouses
- Hard to differentiate how much of the qty is reset by the reco
- Maintain Queue and balances warehouse wise and later aggregate for accurate values

(cherry picked from commit f62b3207ff)
2022-02-14 17:35:37 +00:00
marination
698f910a30 fix: Server Tests and sider
(cherry picked from commit e46a1bc80f)
2022-02-14 17:32:59 +00:00
marination
1df4e2c44a test: Production Plan Pending Qty impact tests
- Two tests to check impact on pending qty: From SO and independent Prod Plan
- Added docstring to each test case for brief summary
- Changed helper function args to fallback to 0 instead of 1 if no arg is passed
- Removed unnecessary `get_doc()`
- Made helper function actions optional depending on args passed

(cherry picked from commit 86ca41b14a)
2022-02-14 17:32:59 +00:00
marination
6800c3efae fix: Initialise pending qty as planned qty for independent item rows in Prod Plan
- Rows that are not fetched from MR or SO, had pending qty 0 throughout
- Initialise pending qty on save only.
- After submit this field will be updated by work order/stock entry
- Bring functions in `validate()` closer to the top

(cherry picked from commit eaccef6116)
2022-02-14 17:32:58 +00:00
marination
c7b9df5aee fix: Set Pending Qty in Prod Plan after updating Work Order
(cherry picked from commit 7116d7ae0e)
2022-02-14 17:32:58 +00:00
Sagar Sharma
7fa46f77e0 revert: "Merge pull request #29290 from s-aga-r/fix/delivery-note/billed-amount" (#29782)
* Revert "Merge pull request #29290 from s-aga-r/fix/delivery-note/billed-amount"

This reverts commit 038f949550, reversing
changes made to c7b4918434.

* fix: linter
2022-02-14 19:54:29 +05:30
Saqib Ansari
53275d4045 Merge pull request #29790 from frappe/mergify/bp/version-13-hotfix/pr-29778
fix: incorrect pricing rule filtering on selecting first item (backport #29778)
2022-02-14 19:13:38 +05:30
Saqib Ansari
47f8b44cfe fix: incorrect pricing rule filtering on selecting first item
(cherry picked from commit 3713ae75ab)
2022-02-14 13:11:57 +00:00
mergify[bot]
e5b50557e6 fix: show user id in emp group table (backport #29776) (#29777)
Co-authored-by: Dany Robert <danyrt@wahni.com>
2022-02-14 10:09:39 +05:30
rohitwaghchaure
f462d5ba38 Merge pull request #29755 from frappe/mergify/bp/version-13-hotfix/pr-29736
fix: time out error while making work orders from production plan (backport #29736)
2022-02-13 22:50:14 +05:30
mergify[bot]
c7fbeb3aac fix(India): Report GSTR-1 minor fixes (#29768)
* fix(India): Report GSTR-1 minor fixes

(cherry picked from commit 6e679a5ad2)

* fix: cleaner implementation for `get_invoice_type`

(cherry picked from commit 2bc157a95c)

Co-authored-by: Smit Vora <mailsmitvora@gmail.com>
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-02-12 20:55:27 +05:30
Deepesh Garg
90f5372fb1 Merge pull request #29716 from deepeshgarg007/loan_repay_via_salary_v2
fix: Loan repayment via Salary Slip
2022-02-12 18:46:38 +05:30
Govind S Menokee
894a406ed4 Bug: fix list mutation within loop
Prevent list mutation within loop leading to incorrect data
2022-02-12 12:12:31 +05:30
Saqib Ansari
e55a27b6a5 Merge pull request #29765 from frappe/mergify/bp/version-13-hotfix/pr-29754
chore: remove deprecated print format (backport #29754)
2022-02-12 12:12:03 +05:30
Sagar Vora
b7b5531ed0 chore: remove deprecated print format
(cherry picked from commit d93d2a80b1)
2022-02-12 06:38:49 +00:00
Rucha Mahabal
672a633154 Merge pull request #29763 from frappe/mergify/bp/version-13-hotfix/pr-29711 2022-02-11 21:43:38 +05:30
Rucha Mahabal
0e3788e346 fix: conflicts 2022-02-11 21:27:44 +05:30
Rucha Mahabal
6a1c27a18f fix(test): reset test setup
(cherry picked from commit cbaadcf113)
2022-02-11 15:55:17 +00:00
Rucha Mahabal
c261621dca test: earned leave allocations based on DOJ
(cherry picked from commit 9b0f9c3442)
2022-02-11 15:55:16 +00:00
Rucha Mahabal
c0ea6d0721 chore: clean-up leave policy assignment tests
(cherry picked from commit 51e6086829)
2022-02-11 15:55:16 +00:00
Rucha Mahabal
9b6040979a fix: consider leaves for past months if assignment is based on joining date too
(cherry picked from commit c7be9ef5d2)

# Conflicts:
#	erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
2022-02-11 15:55:15 +00:00
Rucha Mahabal
3fea6fd9e8 fix: consider based on DOJ config while calculating leaves for passed months
(cherry picked from commit 89fa0bb73f)
2022-02-11 15:55:14 +00:00
Rucha Mahabal
2b4e532226 fix: Earned Leave allocation based on joining date not working
(cherry picked from commit 7326d57966)
2022-02-11 15:55:14 +00:00
Deepesh Garg
540559d6fd Merge branch 'version-13-pre-release' into version-13 2022-02-11 19:14:54 +05:30
Deepesh Garg
5df93916b4 bumped to version 13.20.1 2022-02-11 19:34:54 +05:50
Deepesh Garg
6cb5e85342 Merge pull request #29758 from frappe/mergify/bp/version-13-pre-release/pr-29756
Revert "fix(India): Tax calculation for overseas suppliers" (backport #29756)
2022-02-11 18:06:03 +05:30
Deepesh Garg
5a961dec24 Merge pull request #29757 from frappe/mergify/bp/version-13-hotfix/pr-29756
Revert "fix(India): Tax calculation for overseas suppliers" (backport #29756)
2022-02-11 18:05:08 +05:30
Deepesh Garg
5855225366 Revert "fix(India): Tax calculation for overseas suppliers"
(cherry picked from commit ea20c63182)
2022-02-11 12:11:31 +00:00
Deepesh Garg
87decb3734 Revert "fix(India): Tax calculation for overseas suppliers"
(cherry picked from commit ea20c63182)
2022-02-11 12:11:09 +00:00
Saqib Ansari
31d5d9e1c0 Merge pull request #29752 from frappe/mergify/bp/version-13-hotfix/pr-29732
fix: encode filters for URI (backport #29732)
2022-02-11 17:02:31 +05:30
Rohit Waghchaure
adaa8bd32d fix: time out error while making work orders from prodcution plan
(cherry picked from commit eec2f87088)
2022-02-11 10:43:09 +00:00
barredterra
032e4e4486 fix: encode filters for URI
(cherry picked from commit 5811d9e318)
2022-02-11 09:16:09 +00:00
Saqib Ansari
7629d472a1 Merge pull request #29751 from frappe/mergify/bp/version-13-hotfix/pr-29748
fix: cannot jump to sales invoice in gross profit report (backport #29748)
2022-02-11 14:45:02 +05:30
Saqib Ansari
7c5480d729 fix: cannot jump to sales invoice in gross profit report
(cherry picked from commit 78dd364b0b)
2022-02-11 09:09:38 +00:00
Ankush Menat
5a771dca46 fix: update bin modified timestamp when updating qty
These timestamps are used for writing integrations hence whenever bin is
updated timestamp should update to reliabily use Bin for integration
logic.

(cherry picked from commit 77be98295c)
2022-02-11 12:12:49 +05:30
Saqib Ansari
5fd120a926 Merge pull request #29728 from frappe/mergify/bp/version-13-hotfix/pr-29701
fix(pos): incorrect grand_total in case of inclusive taxes on item (backport #29701)
2022-02-10 11:24:33 +05:30
Ankush Menat
70951bd833 ci: bump ci python version for patch test 2022-02-10 10:57:24 +05:30
Saqib Ansari
c980bce1c8 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29701 2022-02-10 10:53:53 +05:30
Ankush Menat
ffd5efe669 ci: bump ci python version for patch test 2022-02-10 10:53:17 +05:30
Saqib Ansari
6922a91a66 Merge pull request #29726 from frappe/mergify/bp/version-13-hotfix/pr-29374
fix: ignore pricing rule in transactions (backport #29374)
2022-02-10 10:52:35 +05:30
Ankush Menat
d062504593 fix: restrict filetypes to csv for rename tool
(cherry picked from commit c371b52d27)
2022-02-10 10:31:42 +05:30
Saqib Ansari
503cc65db4 fix: restore missing change from develop branch 2022-02-10 10:28:51 +05:30
Saqib Ansari
ec1bafeafc Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29374 2022-02-09 16:45:34 +05:30
Saqib Ansari
d97fa58e4f fix: merge conflicts 2022-02-09 16:44:13 +05:30
Saqib Ansari
41b7c5f92d fix: cancelling of consolidated sales invoice that doesn't have closing entry
(cherry picked from commit 0ebd32dc63)
2022-02-09 11:13:05 +00:00
Saqib Ansari
02cf4e9f25 fix: flaky point of sale test
(cherry picked from commit 4bb557dbd8)
2022-02-09 11:13:05 +00:00
Saqib Ansari
7835e86da0 fix(test): do not enable negative stock
(cherry picked from commit 75256863c6)
2022-02-09 11:13:04 +00:00
Saqib Ansari
e2d8fce3c5 fix(test): case if write off is calculated as negative amount
(cherry picked from commit c2b83a0283)
2022-02-09 11:13:04 +00:00
Saqib Ansari
96034c4396 fix(test): ignore stock validation
(cherry picked from commit afc5c26d1c)
2022-02-09 11:13:04 +00:00
Saqib Ansari
32b55e715a fix(pos): incorrect grand_total in case of inclusive taxes on item
(cherry picked from commit 0452d7de20)
2022-02-09 11:13:03 +00:00
Ankush Menat
a86e8f47e5 Merge pull request #29723 from frappe/mergify/copy/version-13-hotfix/pr-29715
fix: Reserved for Production calculation considered closed work orders (copy #29715)
2022-02-09 15:59:23 +05:30
Rohit Waghchaure
9dec4a2454 Merge branch 'version-13-pre-release' into version-13 2022-02-09 14:44:10 +05:30
Rohit Waghchaure
7e910f21b8 bumped to version 13.20.0 2022-02-09 15:04:10 +05:50
rohitwaghchaure
b27c4ac6e2 Merge pull request #29727 from rohitwaghchaure/change-log-for-v13-20
chore: change log for v13.20.0
2022-02-09 14:42:15 +05:30
Rohit Waghchaure
a451c49a42 chore: change log for v13.20.0 2022-02-09 14:40:07 +05:30
Deepesh Garg
a54e0fe42b test: Update account type in payroll payable account 2022-02-09 13:44:14 +05:30
Saqib Ansari
23a5b3df18 fix: ignore pricing rule in all transactions
(cherry picked from commit ab36b27a94)
2022-02-09 08:02:56 +00:00
Saqib Ansari
91f65da4ec fix(test): pass price_list_rate only if pricing rule has to be removed
(cherry picked from commit 6fa406dd04)
2022-02-09 08:02:55 +00:00
Saqib Ansari
fc67f02ffc test: item price on remove pricing rule
(cherry picked from commit b8c41e3030)

# Conflicts:
#	erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
2022-02-09 08:02:55 +00:00
Saqib Ansari
78c5f34286 fix: ignore pricing rule in all transactions
(cherry picked from commit f6dda738dc)
2022-02-09 08:02:53 +00:00
Deepesh Garg
8da929635f fix: Handle stringified jsons 2022-02-09 12:29:09 +05:30
Ankush Menat
94fc5f95db fix: concflicts 2022-02-09 12:21:38 +05:30
Deepesh Garg
1f6e0b871b fix: Incorrect tax template in Sales Invocie via data import 2022-02-09 11:59:10 +05:30
Deepesh Garg
18ac08413b feat: API method to created consolidated purchase invoice from receipts 2022-02-09 11:58:01 +05:30
Ankush Menat
0ac16f9e63 fix: patch existing bins
(cherry picked from commit e134524532db0473bef48b7e558e5d7a289f090e)

# Conflicts:
#	erpnext/patches.txt
2022-02-09 06:17:27 +00:00
Ankush Menat
197bf8fbec refactor: move reserve quantity computation to work order
(cherry picked from commit a8bf3a3f0d)

# Conflicts:
#	erpnext/stock/doctype/bin/bin.py
2022-02-09 06:17:26 +00:00
Ankush Menat
bde3bece36 test: remove dependency on other tests
(cherry picked from commit d2cc5f2482)
2022-02-09 06:17:25 +00:00
aaronmenezes
bc80f34d92 fix: Reserved for Production calculation considered closed work orders
(cherry picked from commit 6a8b7eeffe)

# Conflicts:
#	erpnext/stock/doctype/bin/bin.py
2022-02-09 06:17:25 +00:00
rohitwaghchaure
a3f1844311 Merge pull request #29552 from frappe/mergify/bp/version-13-hotfix/pr-29359
fix: bypass selling price validation for free item (backport #29359)
2022-02-09 10:39:34 +05:30
Deepesh Garg
2572480554 fix: Loan repayment via Salary Slip 2022-02-08 22:56:14 +05:30
mergify[bot]
e69f70f264 fix: fetch image form item (backport #29523) (#29571)
* fix: fetch image form item (#29523)

(cherry picked from commit 319322228a)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.json

* chore: resolve merge conflicts

Co-authored-by: Himanshu <himanshuwarekar@yahoo.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2022-02-08 21:54:07 +05:30
GangaManoj
e1a9b61103 fix: Pass value instead of array 2022-02-08 17:59:06 +05:30
GangaManoj
df71907be4 fix: Fetch valuation rate 2022-02-08 17:37:38 +05:30
Saqib Ansari
422dcc245f Merge pull request #29707 from frappe/mergify/bp/version-13-hotfix/pr-29560
fix: typeerror on invoice creation from SO/PO (backport #29560)
2022-02-08 17:24:47 +05:30
Deepesh Garg
ca02fa98f8 Merge pull request #29710 from frappe/mergify/bp/version-13-hotfix/pr-29709
fix: currency in bank reconciliation chart (backport #29709)
2022-02-08 16:57:43 +05:30
Saqib Ansari
df870e4b22 fix: currency in bank reconciliation chart
(cherry picked from commit 0fc5d2278d)
2022-02-08 11:03:10 +00:00
Rucha Mahabal
409664387c Merge pull request #29708 from frappe/mergify/bp/version-13-hotfix/pr-29650 2022-02-08 16:31:07 +05:30
Rucha Mahabal
e3246efa5d fix: conflicts 2022-02-08 16:12:24 +05:30
Rucha Mahabal
cc867d6338 fix: conflicts 2022-02-08 15:55:44 +05:30
Rucha Mahabal
a5f016a7cd fix: handle carry forwarded leaves while checking for duplicate allocation
(cherry picked from commit bd1555bd23)
2022-02-08 10:18:34 +00:00
Rucha Mahabal
aa04e02710 fix(test): add ignore duplicates flag to allocation function
(cherry picked from commit e25544f94e)
2022-02-08 10:18:34 +00:00
Rucha Mahabal
ce26759a95 fix: linter
(cherry picked from commit a52ba0a544)
2022-02-08 10:18:34 +00:00
Rucha Mahabal
d26a8d47e4 test: earned leave allocation for passed months and allocation on month-end
(cherry picked from commit 63ee4f1b64)
2022-02-08 10:18:33 +00:00
Rucha Mahabal
5363b1922a fix: earned leaves not allocated if assignment is created on month-end
(cherry picked from commit 25c7f850b1)

# Conflicts:
#	erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
2022-02-08 10:18:33 +00:00
Saqib Ansari
48a9d985e8 Merge pull request #29702 from frappe/mergify/bp/version-13-hotfix/pr-29699
chore: show credit/debit-to account in error message (backport #29699)
2022-02-08 15:28:01 +05:30
ruthra kumar
e5112e16a8 fix: typeerror on invoice creation from SO/PO
(cherry picked from commit 9bd56b0f79)
2022-02-08 09:57:47 +00:00
Saqib Ansari
dddfab1310 fix: Coupon code item pricing dynamic updation issue in pos screen (backport #29599) 2022-02-08 15:27:36 +05:30
Bhavesh Maheshwari
08b9ee7b36 fix: ignore rate validation for work order (#29690)
* fix: ignore rate validation for work order

* test: validate on save instead of on creation

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-08 14:01:20 +05:30
mergify[bot]
bd9a07ed4c fix: dont ignore items that dont have SVD (backport #29656) (#29694)
* fix: dont ignore items that dont have SVD

When items go from negative to positive stock value diff can be zero but
item might have taxes / need divisional loss adjustment.

(cherry picked from commit c88c368880)

* test: check when PR moves stock from neg to pos

(cherry picked from commit d1f5753885)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-08 13:52:41 +05:30
mergify[bot]
cc5107eab3 fix: ignore cancelled SLEs (#29679) (#29698)
(cherry picked from commit 0ca60afc3f)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-08 13:52:28 +05:30
Subin Tom
c494ed087a fix: Coupon code item pricing dynamic updation issue
(cherry picked from commit ccf63124d6)
2022-02-08 06:20:51 +00:00
Saqib Ansari
52a74921c0 chore: show credit/debit-to account in error message
(cherry picked from commit e93bb8a336)
2022-02-08 06:13:31 +00:00
Ankush Menat
8e660890f4 test: commit item/warehouse creation to db
(cherry picked from commit f82d7eb73f)
2022-02-07 23:16:18 +05:30
Ankush Menat
29d3a189cb chore: drop dead field from stock reconciliation
(cherry picked from commit 43f8ee1dd1)
2022-02-07 23:16:18 +05:30
Ankush Menat
19f99a1c5f test: product bundle reposting
(cherry picked from commit 699519f7b6)
2022-02-07 23:16:18 +05:30
Ankush Menat
86d30ed583 test: move bundle info to class variables
(cherry picked from commit 853e658dcc)
2022-02-07 23:16:18 +05:30
Ankush Menat
6f6b474333 fix: merge stock ledger item warehouse with doc's
(cherry picked from commit 1022db0474)
2022-02-07 23:16:18 +05:30
Ankush Menat
eeb8f4874d refactor: simplify get_items_and_warehouses
Also remove dead code related to stock reconciliation_json.

(cherry picked from commit e6ab8df8f2)
2022-02-07 23:16:18 +05:30
Ankush Menat
bd401810e4 fix: consider packed items too when reposting
(cherry picked from commit c56d07dee3)
2022-02-07 23:16:18 +05:30
Ritwik Puri
c137b141e8 fix: use item_code instead of parent field in bom_stock_calculated report (#29684)
(cherry picked from commit 72a812f18b)
2022-02-07 23:11:40 +05:30
Deepesh Garg
cace76b355 Merge pull request #29682 from frappe/mergify/bp/version-13-hotfix/pr-29349
feat: option to disable Item Tax Template and Tax Category (backport #29349)
2022-02-07 22:26:09 +05:30
Deepesh Garg
fbf6b4e5aa Merge pull request #29677 from frappe/mergify/bp/version-13-hotfix/pr-29460
feat: Refund entry against loans (backport #29460)
2022-02-07 19:17:32 +05:30
Deepesh Garg
427d8c865a Merge pull request #29678 from frappe/mergify/bp/version-13-pre-release/pr-29460
feat: Refund entry against loans (backport #29460)
2022-02-07 19:17:07 +05:30
Deepesh Garg
146cf89588 Merge pull request #29685 from frappe/mergify/bp/version-13-hotfix/pr-29660
fix: Add missing key in Loan (backport #29660)
2022-02-07 19:05:38 +05:30
Deepesh Garg
5ed44bdb88 Merge pull request #29686 from frappe/mergify/bp/version-13-pre-release/pr-29660
fix: Add missing key in Loan (backport #29660)
2022-02-07 19:05:06 +05:30
Deepesh Garg
eb36ebee8f fix: Resolve conflicts 2022-02-07 18:49:57 +05:30
Deepesh Garg
066ceb06cd fix: Resolve conflicts 2022-02-07 18:46:53 +05:30
Deepesh Garg
159e492053 test: Add test case for repayment against partially disbursed loans
(cherry picked from commit ef69d1fd38)
2022-02-07 13:16:19 +00:00
Abhinav Raut
6dbb3cdf75 fix: missing key in loan
(cherry picked from commit d0043bdbac)
2022-02-07 13:16:19 +00:00
Deepesh Garg
4196f04f49 test: Add test case for repayment against partially disbursed loans
(cherry picked from commit ef69d1fd38)
2022-02-07 13:15:42 +00:00
Abhinav Raut
9396143009 fix: missing key in loan
(cherry picked from commit d0043bdbac)
2022-02-07 13:15:41 +00:00
barredterra
ec55251f6a feat: option to disable tax category
(cherry picked from commit 663c594ead)
2022-02-07 12:11:14 +00:00
barredterra
501ce90796 feat: option to disable Item Tax Template
(cherry picked from commit f60e040d69)
2022-02-07 12:11:13 +00:00
Deepesh Garg
25012399fa fix: Add disbursement accounts to tests
(cherry picked from commit 8ece2845f2)
2022-02-07 09:09:20 +00:00
Deepesh Garg
4ab6994b3d feat: Refund entry against loans
(cherry picked from commit c68c70f8bc)

# Conflicts:
#	erpnext/loan_management/doctype/loan/loan.json
#	erpnext/patches.txt
2022-02-07 09:09:19 +00:00
Deepesh Garg
f4e2c1fad3 fix: Add disbursement accounts to tests
(cherry picked from commit 8ece2845f2)
2022-02-07 09:08:48 +00:00
Deepesh Garg
946e560c55 feat: Refund entry against loans
(cherry picked from commit c68c70f8bc)

# Conflicts:
#	erpnext/loan_management/doctype/loan/loan.json
#	erpnext/patches.txt
2022-02-07 09:08:48 +00:00
mergify[bot]
8cadc2668d fix(ux): make stock entry type the title field (#29674) (#29675)
(cherry picked from commit 5e6227e3d8)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-07 13:20:34 +05:30
Deepesh Garg
fd4e0f1d40 Merge pull request #29666 from frappe/mergify/bp/version-13-hotfix/pr-29659
fix: Billing status for zero amount ref doc (backport #29659)
2022-02-06 21:56:34 +05:30
Deepesh Garg
3c032146ad Merge pull request #29668 from frappe/mergify/bp/version-13-hotfix/pr-29601
fix: Incorrect provisional profit and loss in balance sheet (backport #29601)
2022-02-06 21:55:54 +05:30
Deepesh Garg
dcff8e74fc Merge pull request #29669 from frappe/mergify/bp/version-13-hotfix/pr-29609
fix: Zero rated exports in GSTR-3B report (backport #29609)
2022-02-06 21:55:15 +05:30
Deepesh Garg
00af714743 Merge pull request #29661 from frappe/mergify/bp/version-13-hotfix/pr-29641
fix: Ignore linked invoices on Journal Entry cancel (backport #29641)
2022-02-06 21:53:12 +05:30
Ankush Menat
3e79533112 fix: dont show cancelled PO items in plan report
(cherry picked from commit 6459ceaea1)
2022-02-06 20:07:31 +05:30
Ankush Menat
e618571fb3 fix: dont show cancelled PO items in plan report
(cherry picked from commit 6459ceaea1)
2022-02-06 20:07:22 +05:30
Deepesh Garg
aca788cbe0 fix: Zero rated exports in GSTR-3B report
(cherry picked from commit 233e6449fc)
2022-02-06 14:04:46 +00:00
Deepesh Garg
d10423ef1f fix: Incorrect provisional profit and loss in balance sheet
(cherry picked from commit ed2c6b6637)
2022-02-06 14:03:41 +00:00
Deepesh Garg
62b71f9edd fix: Add test case
(cherry picked from commit c5726fd3da)
2022-02-06 13:52:58 +00:00
Deepesh Garg
3b47a55932 fix: Billing status for zero amount ref doc
(cherry picked from commit d679692472)
2022-02-06 13:52:57 +00:00
Deepesh Garg
5acf773042 Merge pull request #29635 from deepeshgarg007/sales_invoice_tax_template
fix: Incorrect tax template in Sales Invoice via data import
2022-02-06 18:58:43 +05:30
Deepesh Garg
f5f1afde15 fix: Ignore linked invoices on Journal Entry cancel
(cherry picked from commit 6f7ae62907)
2022-02-06 13:27:34 +00:00
Ankush Menat
65d4670f45 test: regression test for LCV GL entries
(cherry picked from commit 69c65afd72)
2022-02-06 12:31:27 +05:30
Ankush Menat
689f8cb537 fix: ignore cancelled svd while updating GLE for PR
This happens because LCV cancels and reposts entries so unless filtered
by non-cancelled entries you can randomly get old values.

(cherry picked from commit 8858c703a8)
2022-02-06 12:31:27 +05:30
Marica
c99a14a7f3 Merge pull request #29646 from frappe/mergify/bp/version-13-pre-release/pr-29642
fix: Regenerate packed items on newly mapped doc (backport #29642)
2022-02-04 23:25:17 +05:30
Marica
5bf994a82a Merge pull request #29645 from frappe/mergify/bp/version-13-hotfix/pr-29642
fix: Regenerate packed items on newly mapped doc (backport #29642)
2022-02-04 23:24:15 +05:30
marination
db24fca8c1 fix: Regenerate packed items on newly mapped doc
- Cannot determine action on newly mapped DN that hasnt been inserted
- Rows could have been deleted, updated, added, etc. before first save
- In this case , reset packing table

(cherry picked from commit bd41a99c8a)
2022-02-04 17:33:17 +00:00
mergify[bot]
2628e2faff fix: enable Allow on Submit for 'Is Active' field in Salary Structure (backport #29630) (#29643)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-04 23:02:45 +05:30
marination
3342efc388 fix: Regenerate packed items on newly mapped doc
- Cannot determine action on newly mapped DN that hasnt been inserted
- Rows could have been deleted, updated, added, etc. before first save
- In this case , reset packing table

(cherry picked from commit bd41a99c8a)
2022-02-04 17:32:09 +00:00
Deepesh Garg
20f321a889 fix: Incorrect tax template in Sales Invocie via data import 2022-02-04 12:01:07 +05:30
Marica
e095e27cb9 Merge pull request #29629 from frappe/mergify/bp/version-13-pre-release/pr-29456
fix: Incorrect packing list for recurring items & code cleanup (backport #29456)
2022-02-04 10:17:46 +05:30
Marica
51f9f42fab Merge pull request #29628 from frappe/mergify/bp/version-13-hotfix/pr-29456
fix: Incorrect packing list for recurring items & code cleanup (backport #29456)
2022-02-04 10:17:25 +05:30
marination
bdfb478b3b fix: (Linter) import sequence
(cherry picked from commit f18ec2d947)
2022-02-03 16:41:20 +00:00
marination
2428812dcc test: Packed Items test file
(cherry picked from commit 4e7b4dc9a8)
2022-02-03 16:41:20 +00:00
marination
677a3975ba refactor: Price fetching and updation logic
- fetch price from price list, use item master valuation rate as fallback fo0r packed item
- use a item code, item row name map to maintain cumulative price
- reset table if item in a row is replaced
- loop over items table only to set price, lesser iterations than packed items table

(cherry picked from commit 2f4d266ee1)
2022-02-03 16:41:19 +00:00
marination
5d37c9c38d chore: Break updation logic into smaller functions
- Smaller functions for updation
- All calls visible from parent function to avoid context switching due to nested calls

(cherry picked from commit 2c14ab0439)
2022-02-03 16:41:19 +00:00
marination
b836d4fe25 fix: Linter and minor code refactor
- Create an indexed map of stale packed items table to avoid loops to check if packed item row exists
- Reset packed items if row deletion takes place
- Renamed functions to self-explain them
- Split long function
- Reduce function calls inside function (makes it harder to follow through)

(cherry picked from commit 4c677eafe9)
2022-02-03 16:41:18 +00:00
marination
153fd355bc chore: SQL to QB & accomodate Update Items
- `doc_before_save` does not exist via Update Items (updates stuff in the backend so doc isn't considered unsaved/dirty)
- converted more raw sql to qb and ORM

(cherry picked from commit f8a5786542)
2022-02-03 16:41:18 +00:00
marination
350cac9207 fix: Incorrect packing list for recurring items & code cleanup
- Fix Incorrect packing list for recurring items in the Items table
- Re-organised functions based on external use and order of use
- Deleted `clean_packing_list` function and reduced no.of loops
- Raw SQL to QB
- Minor formatting changes

(cherry picked from commit 3f48fc1898)
2022-02-03 16:41:17 +00:00
marination
3fea24f9db fix: (Linter) import sequence
(cherry picked from commit f18ec2d947)
2022-02-03 16:40:46 +00:00
marination
f33a1e858f test: Packed Items test file
(cherry picked from commit 4e7b4dc9a8)
2022-02-03 16:40:45 +00:00
marination
fc35a74f76 refactor: Price fetching and updation logic
- fetch price from price list, use item master valuation rate as fallback fo0r packed item
- use a item code, item row name map to maintain cumulative price
- reset table if item in a row is replaced
- loop over items table only to set price, lesser iterations than packed items table

(cherry picked from commit 2f4d266ee1)
2022-02-03 16:40:45 +00:00
marination
cc22cbe50b chore: Break updation logic into smaller functions
- Smaller functions for updation
- All calls visible from parent function to avoid context switching due to nested calls

(cherry picked from commit 2c14ab0439)
2022-02-03 16:40:45 +00:00
marination
6e7f1a300c fix: Linter and minor code refactor
- Create an indexed map of stale packed items table to avoid loops to check if packed item row exists
- Reset packed items if row deletion takes place
- Renamed functions to self-explain them
- Split long function
- Reduce function calls inside function (makes it harder to follow through)

(cherry picked from commit 4c677eafe9)
2022-02-03 16:40:44 +00:00
marination
c59c5bf2c6 chore: SQL to QB & accomodate Update Items
- `doc_before_save` does not exist via Update Items (updates stuff in the backend so doc isn't considered unsaved/dirty)
- converted more raw sql to qb and ORM

(cherry picked from commit f8a5786542)
2022-02-03 16:40:44 +00:00
marination
596a3a65c2 fix: Incorrect packing list for recurring items & code cleanup
- Fix Incorrect packing list for recurring items in the Items table
- Re-organised functions based on external use and order of use
- Deleted `clean_packing_list` function and reduced no.of loops
- Raw SQL to QB
- Minor formatting changes

(cherry picked from commit 3f48fc1898)
2022-02-03 16:40:44 +00:00
mergify[bot]
b93d8f8716 feat: show stock value difference in stock ledger report (#29607) (#29621)
(cherry picked from commit 04f6426dbf)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-03 17:17:33 +05:30
Ankush Menat
862a90bb15 Merge pull request #29603 from s-aga-r/fix/production-plan/ordered_qty
fix: Multiple WO for a single Production Plan Item
2022-02-03 15:26:02 +05:30
Ankush Menat
22ff462abb fix: add default if qty is not found 2022-02-03 15:25:34 +05:30
mergify[bot]
c054d1f7ee fix: ignore empty customer/supplier in item query (#29610) (#29614)
* fix: dont try to filter by customer/supplier if None

* test: item query with emtpy supplier

(cherry picked from commit 41a95e5624)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-03 14:44:33 +05:30
mergify[bot]
754a3d65f0 fix: ignore empty customer/supplier in item query (#29610) (#29613)
* fix: dont try to filter by customer/supplier if None

* test: item query with emtpy supplier

(cherry picked from commit 41a95e5624)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-03 13:22:15 +05:30
Sagar Sharma
35ee119c99 Merge branch 'version-13-hotfix' into fix/production-plan/ordered_qty 2022-02-03 11:08:35 +05:30
Sagar Sharma
36609c50c0 test: add test case for multiple WO 2022-02-02 23:04:19 +05:30
Saqib Ansari
03248ea483 Merge pull request #29598 from frappe/mergify/bp/version-13-hotfix/pr-29597
fix(pos): pricing rule on transactions doesn't work (backport #29597)
2022-02-02 21:45:49 +05:30
Saqib Ansari
6dd5ed68b4 Merge pull request #29446 from frappe/mergify/bp/version-13-hotfix/pr-29426
fix: Use get for conditionally available fields while setting missing values (backport #29426)
2022-02-02 21:45:11 +05:30
Saqib Ansari
34f223a144 Merge pull request #29565 from frappe/mergify/bp/version-13-hotfix/pr-29556
feat: Allowing non stock items in POS (backport #29556)
2022-02-02 21:44:02 +05:30
Saqib Ansari
e2f5ddb20a fix(pos): pricing rule on transactions doesn't work
(cherry picked from commit e082e3f702)
2022-02-02 14:18:46 +00:00
mergify[bot]
0608f61b80 fix: Program Enrollment tests (backport #29592) (#29594)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-02 17:51:46 +05:30
Sagar Sharma
4e58c1da3a fix: ordered_qty for production-plan-item 2022-02-02 14:56:11 +05:30
Deepesh Garg
68b337316a Merge pull request #29573 from frappe/mergify/bp/version-13-pre-release/pr-29561
feat: Provisional accounting for expenses (backport #29451) (backport #29561)
2022-02-01 20:39:09 +05:30
mergify[bot]
535e6d61f0 fix: employee reminders fixes (backport #29548) (#29576)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-01 20:22:49 +05:30
Deepesh Garg
1ab3714fd0 chore: Resolve conflicts 2022-02-01 19:41:00 +05:30
Deepesh Garg
dbb3ea3a6e chore: fix tests
(cherry picked from commit 640b2d57de)
2022-02-01 12:42:09 +00:00
Deepesh Garg
abfccde664 fix: Linting Issues
(cherry picked from commit c71dbda166)
2022-02-01 12:42:09 +00:00
Deepesh Garg
293ec00023 chore: Resolve conflicts
(cherry picked from commit aa16a4bb5f)

# Conflicts:
#	erpnext/patches.txt
2022-02-01 12:42:09 +00:00
Deepesh Garg
061a569f4d feat: Provisional accounting for expenses (#29451)
* feat: Provisonal accounting for expenses

* fix: Method for provisional accounting entry

* chore: Add test case

* fix: Remove test case

* fix: Use company doctype

* fix: Add provisional expense account field in Purchase Receipt Item

* fix: Test case

* fix: Move provisional expense account to parent

* fix: Patch

(cherry picked from commit 528c71382f)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/patches.txt
#	erpnext/setup/doctype/company/company.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
(cherry picked from commit a54c84728f)

# Conflicts:
#	erpnext/patches.txt
2022-02-01 12:42:08 +00:00
Deepesh Garg
b9b42c11e6 Merge pull request #29561 from frappe/mergify/bp/version-13-hotfix/pr-29451
feat: Provisional accounting for expenses (backport #29451)
2022-02-01 18:11:37 +05:30
mergify[bot]
ffbf7ec3b7 fix: don't show "create" on cancelled BOMs (#29570) (#29572)
(cherry picked from commit a3a05c0c23)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-02-01 17:44:13 +05:30
Deepesh Garg
640b2d57de chore: fix tests 2022-02-01 17:25:26 +05:30
Saqib Ansari
c0a2388ab2 fix: Validation for invalid serial nos at POS invoice level (backport #29447) 2022-02-01 16:39:22 +05:30
Saqib Ansari
4778f16c56 fix: flaky test
(cherry picked from commit bf70feb7c9)
2022-02-01 11:07:33 +00:00
Saqib Ansari
33e8cd9cde chore: remove unused import
(cherry picked from commit 4e4159ec06)
2022-02-01 11:07:33 +00:00
Saqib Ansari
04acbea93d chore: remove useless rollback
(cherry picked from commit 2aca54eb38)
2022-02-01 11:07:32 +00:00
Saqib Ansari
c8f2a7fc69 test: point of sale search
(cherry picked from commit 650d44a714)
2022-02-01 11:07:32 +00:00
Subin Tom
7815ffb842 fix: remove qty indicator from non stock items
(cherry picked from commit 845c02a989)
2022-02-01 11:07:31 +00:00
Subin Tom
c57d184576 fix: sider fix
(cherry picked from commit 27b35d72e2)
2022-02-01 11:07:30 +00:00
Subin Tom
8ed2a7bc2c fix: removing get_value call by returning is_stock_item in get_stock_availability method
(cherry picked from commit ac9a9fb229)
2022-02-01 11:07:30 +00:00
Subin Tom
15c6b93ee3 fix: Removed validate_non_stock_items, filter_service_items methods
(cherry picked from commit 9e20fa85c1)
2022-02-01 11:07:29 +00:00
Subin Tom
0aff20e85c fix: sider issues
(cherry picked from commit 6b973d658c)
2022-02-01 11:07:28 +00:00
Subin Tom
75faa151fd fix: Allowing non stock items in POS
(cherry picked from commit ec37930404)
2022-02-01 11:07:28 +00:00
mergify[bot]
e2bdd5efdb fix: leave application tests (backport #29539) (#29563)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-01 16:18:16 +05:30
Deepesh Garg
c71dbda166 fix: Linting Issues 2022-02-01 15:08:45 +05:30
Deepesh Garg
aa16a4bb5f chore: Resolve conflicts 2022-02-01 15:02:23 +05:30
Deepesh Garg
a54c84728f feat: Provisional accounting for expenses (#29451)
* feat: Provisonal accounting for expenses

* fix: Method for provisional accounting entry

* chore: Add test case

* fix: Remove test case

* fix: Use company doctype

* fix: Add provisional expense account field in Purchase Receipt Item

* fix: Test case

* fix: Move provisional expense account to parent

* fix: Patch

(cherry picked from commit 528c71382f)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/patches.txt
#	erpnext/setup/doctype/company/company.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2022-02-01 09:13:47 +00:00
mergify[bot]
2b83dabae1 fix: do not hide Loan Repayment Entry field in salary slip (backport #29535) (#29551)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-02-01 12:52:30 +05:30
Krithi Ramani
f5bb778d37 bypass selling price validation for free item
(cherry picked from commit 8c536ffb20)
2022-02-01 06:55:41 +00:00
Saqib Ansari
7731a2984c Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29447 2022-02-01 11:12:13 +05:30
Deepesh Garg
038f949550 Merge pull request #29290 from s-aga-r/fix/delivery-note/billed-amount
fix: billed amount in delivery note items
2022-02-01 11:08:49 +05:30
rohitwaghchaure
8c0613a600 Merge pull request #29542 from rohitwaghchaure/merge-branch-hotfix-to-pre-release-for-13-20
chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release'
2022-01-31 20:16:11 +05:30
Rohit Waghchaure
862961d92e chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release' 2022-01-31 19:38:44 +05:30
Saqib Ansari
96efdf124e Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29447 2022-01-31 19:34:54 +05:30
Saqib Ansari
c7b4918434 Merge pull request #29388 from frappe/mergify/bp/version-13-hotfix/pr-29373
fix: cost center validation of asset (backport #29373)
2022-01-31 19:34:27 +05:30
Sagar Sharma
c4155016d4 Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount 2022-01-31 19:13:21 +05:30
Sagar Sharma
fedeb2a70f chore: remove patch 2022-01-31 19:07:07 +05:30
mergify[bot]
5ac11a4ba5 refactor!: dynamically compute bom_level (backport #29522) (#29540)
* refactor!: dynamically compute bom_level

(cherry picked from commit 157b4b33fe)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/bom.py
#	erpnext/patches.txt

* fix(ux): sort multi-production item plans correctly

(cherry picked from commit d38fd8635c)

* fix: conflicts

* fix: removed set_bom_level

* fix: conflict

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2022-01-31 17:11:43 +05:30
Ankush Menat
2bc42759d2 Merge pull request #29534 from frappe/mergify/bp/version-13-hotfix/pr-29519
fix: bin uniqueness (backport #29519)
2022-01-31 15:56:12 +05:30
mergify[bot]
9425ca07f5 chore: revert manual handling of stock level section (#29537) (#29538)
(cherry picked from commit a254201696)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-31 15:43:08 +05:30
Ankush Menat
2b868e7f56 fix: update reserved qty for production/ s/c
(cherry picked from commit 0a15334464)
2022-01-31 09:00:20 +00:00
Ankush Menat
f99f537b57 refactor: patch for fixing broken bins
fix(patch): delete fully broken bins

if bin doesn't have item_code or warehouse then it's not recoverable.

(cherry picked from commit c2ecc7a2d1)
2022-01-31 09:00:19 +00:00
Sagar Sharma
a7b30d9197 fix(patch): patch duplicate bins
(cherry picked from commit 7ff3ca25e5)
2022-01-31 09:00:18 +00:00
Ankush Menat
b3f36efa8f fix: ignore None item/wh whie updating reservation
(cherry picked from commit a1e7771cdd)
2022-01-31 09:00:17 +00:00
Ankush Menat
8aa6423093 fix: add unique constraint on bin at db level
(cherry picked from commit 08810dbcce)
2022-01-31 09:00:16 +00:00
Subin Tom
86a560eeb0 fix: Validation for invalid serial nos at POS invoice level (#29447)
(cherry picked from commit 05bbb69d0e)
2022-01-31 07:39:10 +00:00
Saqib Ansari
144c8ec904 Merge pull request #29528 from frappe/mergify/bp/version-13-hotfix/pr-29454
fix(sales order): incorrect no. of items fetched while creating dn (backport #29454)
2022-01-31 12:16:30 +05:30
Saqib Ansari
bed7201efe fix: merge conflicts 2022-01-31 11:40:59 +05:30
Saqib Ansari
e6f2343fb4 chore: add comment
(cherry picked from commit 0b9a850a17)
2022-01-31 06:09:26 +00:00
Saqib Ansari
9da63f2e79 fix(sales order): incorrect no. of items fetched while creating dn
(cherry picked from commit a73ad6759d)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.js
2022-01-31 06:09:26 +00:00
Saqib Ansari
f31e52123a fix: qty filter not working if apply_multiple_pricing_rules is enabled 2022-01-31 11:37:33 +05:30
Deepesh Garg
2c83f18864 Merge pull request #29518 from frappe/mergify/bp/version-13-hotfix/pr-29499
fix(India): Tax calculation for overseas suppliers (backport #29499)
2022-01-31 11:06:51 +05:30
mergify[bot]
0fb4e81a30 fix: cost of poor quality report time filters not working (#28958) (#29521)
* fix: cost of poor quality report time filters not working

* chore:update cost of poor quality report to use query builder

* fix: linter warnings

* chore: updated report query

* chore: added test filters

* fix : cleared linter warnings

* chore: formatting

* refactor: query generation

- optionally apply date filters
- join instead of expensive sub-query
- return as dictionary

* test: simplify test

Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 0f7c2a19de)

Co-authored-by: aaronmenezes <ron2805@gmail.com>
2022-01-30 20:49:41 +05:30
Deepesh Garg
ab0e63fe13 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29426 2022-01-30 17:30:10 +05:30
Deepesh Garg
dc82becbd2 fix(India) Tax calculation for overseas suppliers
(cherry picked from commit e45c38337c)
2022-01-30 07:44:43 +00:00
Ankush Menat
70dc77305b fix: work order serial no allows storing whitespace
(cherry picked from commit 43bd88e741)
2022-01-29 14:27:09 +05:30
Ankush Menat
db3eef9a8b fix: extend sr_no cleanup to packed items too
(cherry picked from commit e177c5277f)
2022-01-29 14:27:09 +05:30
Ankush Menat
cc08125f39 refactor: reusable clean_serial_nos
(cherry picked from commit b20df3745e)
2022-01-29 14:27:09 +05:30
Ankush Menat
997ddbea78 fix: ignore alternate item while checking pending qty
(cherry picked from commit 14e3e163ae)
2022-01-29 14:02:10 +05:30
Deepesh Garg
ebe9599dda Merge pull request #29428 from deepeshgarg007/shipping_rule_apply
fix: Add shipping charges to taxes only if applicable
2022-01-29 12:56:49 +05:30
Saqib Ansari
8535ad5294 chore: undo unnecessary changes
(cherry picked from commit 3da2cac772)
2022-01-29 07:14:23 +00:00
Saqib Ansari
eeb2402764 chore: rename redefinition of multiple pricing rule test
(cherry picked from commit 5a7a9a598b)
2022-01-29 07:14:22 +00:00
Saqib Ansari
e7e0359e3e test: add test_multiple_pricing_rules
(cherry picked from commit c0d1f4869f)
2022-01-29 07:14:22 +00:00
Saqib Ansari
c24724ac7a fix: qty filter not working if apply_multiple_pricing_rules is enabled
(cherry picked from commit 1e17d6a607)
2022-01-29 07:14:22 +00:00
Deepesh Garg
5f9fdba49e Merge branch 'version-13-hotfix' into shipping_rule_apply 2022-01-29 12:41:14 +05:30
Sagar Sharma
acd39ff6ae Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount 2022-01-28 15:38:26 +05:30
Ankush Menat
5dd443eeb0 test: delete data after running test
[skip ci]
2022-01-28 13:52:20 +05:30
Ankush Menat
de01f0f364 fix(patch): update BOMs to have correct transfer_material_against
(cherry picked from commit e4c220053f)
2022-01-28 13:52:20 +05:30
Ankush Menat
4e14d28564 fix: ensure correct-ish transfer against
(cherry picked from commit 86efa0dbfd)
2022-01-28 13:52:20 +05:30
Ankush Menat
03bd68654b chore: disable annoying flake8 formatting for QB (#29488)
[skip ci]

(cherry picked from commit 251576a6bb)
2022-01-28 13:50:02 +05:30
mergify[bot]
67689e7374 fix: stock ledger rendering glitch (#29482)
undefined Issue

(cherry picked from commit f380042195)

Co-authored-by: smehata <53169014+smehata@users.noreply.github.com>
(cherry picked from commit 0dafa8a12a)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-01-27 20:24:51 +05:30
Suraj Shetty
4e4855fed7 Merge pull request #29473 from frappe/mergify/bp/version-13-hotfix/pr-29471 2022-01-27 11:27:29 +05:30
Suraj Shetty
352c585ef5 fix: Further sort sales_order_analysis to get consistent response
Since the data was sorted by `transaction_date`, report sorting order used to be inconsistent everytime if there were lots of records with same transaction date. This used to create problems while exporting report as "Excel" with filters.

(cherry picked from commit 29c7947db0)
2022-01-27 05:46:44 +00:00
Akash Krishna
d2b61bbe8a fix(healthcare): populate current practitioner in appointment availability popup (#29405)
* fix: Remove two-way patient fetch from inpatient_record (Lab Test, Vital Signs, Sample Collection)

* fix: Patient Appointment - practitioner field gettng cleared on Check Availability dialog, added Practitioner name in the dialog

* fix: Patient Appointment - set practitioner in check availability dialog load

Co-authored-by: Chillar Anand <chillar@avilpage.com>
2022-01-26 14:13:40 +05:30
mergify[bot]
58fd7044b5 fix: remove all stock UOM conversion when changing stock UOM (#29463)
(cherry picked from commit eb8d08b411)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-26 12:28:26 +05:30
Rucha Mahabal
177a209f50 fix(Employee): set user image and validate user id only if user data is found (#29452) 2022-01-25 16:49:53 +05:30
marination
c6273b32ee fix: Use get for conditionally available fields while setting missing values
- Due to custom field "supplier" and missing field "supplier_address", dot operator breaks
- Make sure to use "get" instead of just dot operator if field is in some doctypes, not all

(cherry picked from commit 78b6b29a57)
2022-01-25 07:22:45 +00:00
Saurabh
034773a670 fix: broken patches (#29067)
* chore: patch fixes

* fix: remove desktop icons while deleting sales reports

* refactor: dont ignore dangerous exceptions in patches

* fix: make patch kinda idempotent

with previous query rerunning would've caused all values to become 0.

* fix: check type before patching

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-25 11:12:25 +05:30
mergify[bot]
63ccddfcc9 fix: fetch "transfer material against" from BOM (#29435) (#29436)
(cherry picked from commit b8fbfed096)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-24 22:10:28 +05:30
Deepesh Garg
ecba064161 Merge pull request #29404 from deepeshgarg007/doc_currency_override
fix: Previous document currency getting overridden
2022-01-24 20:49:09 +05:30
mergify[bot]
895dd68691 fix: honour 'include holidays' setting while marking attendance for leave application (backport #29425) (#29432)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-01-24 19:21:58 +05:30
mergify[bot]
d361e23a59 test: clear singles cache for changed settings (#29430) (#29431)
(cherry picked from commit 3d3f0139fd)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-24 18:46:20 +05:30
Deepesh Garg
e8bc4f12a7 fix:Add shipping charges to taxes only if applicable 2022-01-24 16:16:27 +05:30
rohitwaghchaure
aba8788ea2 Merge pull request #29422 from frappe/mergify/bp/version-13-hotfix/pr-29419
fix: incorrect raw materials quantity in manufacture stock entry (backport #29419)
2022-01-24 15:32:38 +05:30
rohitwaghchaure
3dfe7faf70 fix: issue of CI 2022-01-24 13:11:13 +05:30
Deepesh Garg
f7593f599e Merge pull request #29424 from frappe/mergify/bp/version-13-hotfix/pr-29360
test: unit test for deferred item with zero months (backport #29360)
2022-01-24 13:01:49 +05:30
Deepesh Garg
51518cf978 Merge pull request #29423 from frappe/mergify/bp/version-13-hotfix/pr-29407
fix: opening invoice creation tool can fetch multiple accounting dimension (backport #29407)
2022-01-24 13:01:11 +05:30
Deepesh Garg
f6ee0c70e2 Merge branch 'version-13-hotfix' into doc_currency_override 2022-01-24 12:48:53 +05:30
ruthra
abd8291012 test: unit test for deferred items with zero months
(cherry picked from commit 1e0450ed17)
2022-01-24 07:12:37 +00:00
ruthra
37bbdc6743 refactor: a more generalized way to clear old entries for test cases
(cherry picked from commit ed0f9cc063)
2022-01-24 07:12:36 +00:00
ruthra
24011bc6a4 refactor: fix failing test cases
(cherry picked from commit f122a47d0d)
2022-01-24 07:07:04 +00:00
ruthra
1273221477 fix: linter issue
(cherry picked from commit 80ea163953)
2022-01-24 07:07:04 +00:00
ruthra
b96fa03228 test: validating custom accouting dimension in opening invoice tool
- create new dimension 'department'
 - assign and validate department in opening invoices

(cherry picked from commit 88463dc494)
2022-01-24 07:07:03 +00:00
ruthra
bbd777a9cb fix: opening invoice creation tool not taking in accouting dimension
(cherry picked from commit e2267cdba8)
2022-01-24 07:07:03 +00:00
rohitwaghchaure
384c6234d5 fix: conflicts 2022-01-24 12:16:19 +05:30
Rohit Waghchaure
540690252f fix: incorrect raw materials quantity in manufacture stock entry
(cherry picked from commit f8d232ac26)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2022-01-24 06:44:46 +00:00
rohitwaghchaure
43a21f2836 Merge pull request #29337 from anupamvs/crm-contact-duplication-v13
fix: contact duplication on converting lead to customer
2022-01-24 10:16:23 +05:30
Saqib Ansari
ddf5ffc47e Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29373 2022-01-23 18:17:56 +05:30
mergify[bot]
2fbaba40b5 fix: bypass_credit_limit_check in credit balance report (#29401) 2022-01-23 18:17:30 +05:30
Britlog
096e7b9ec5 fix: Don't delete barcode while saving Item (#29411) 2022-01-23 16:56:09 +05:30
Marica
315d542c2a Merge pull request #29398 from marination/minor-quotation-doc-wrong-var
fix: Wrong variable name breaks Cart Items rendering
2022-01-21 19:20:29 +05:30
marination
bfbcde7595 debug: CI 2022-01-21 19:02:47 +05:30
Deepesh Garg
ef782fd35b fix: Previous document currency getting overriden 2022-01-21 18:00:35 +05:30
Ankush Menat
1538076ae4 fix(patch): skip patch if column doesn't exist 2022-01-21 16:11:32 +05:30
Ankush Menat
f60ea20145 test: create and cancel sr no with stock reco
(cherry picked from commit 4fa93e246e)
2022-01-21 16:11:32 +05:30
Ankush Menat
91887f215d fix(patch): update stock reconciliation current serial nos
When current serial nos are non-existing there shouldn't be any value in
current_serial_no field.

(cherry picked from commit 203f0086f8)
2022-01-21 16:11:32 +05:30
Ankush Menat
0e413ef54e fix: bad default for non-existing serial nos
serial_no field should have empty string if it doesn't have any serial
nos

(cherry picked from commit 33c4a0b5a9)
2022-01-21 16:11:32 +05:30
Ankush Menat
f336b5d586 perf: single query to update serial nos
(cherry picked from commit ea5efd0791)
2022-01-21 16:11:32 +05:30
marination
2be15bd8e0 fix: Wrong variable name breaks Cart Items rendering 2022-01-21 15:58:29 +05:30
mergify[bot]
d9f783f38e fix: update status on visit cancel (#29389) (#29394)
* fix: update status on cancelling visit

* test: updated test for cancelling visits

(cherry picked from commit 11c9a5a79f)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
2022-01-21 15:22:56 +05:30
Saqib Ansari
6aaa187a49 Merge pull request #29392 from frappe/mergify/bp/version-13-hotfix/pr-29385
fix: first depreciation amount in asset (backport #29385)
2022-01-21 15:15:55 +05:30
Marica
f2d8d7ab96 Merge pull request #29383 from marination/item-variant-web-logic-fix
fix: Cart & Popup Logic of Item variant without Website Item
2022-01-21 15:15:33 +05:30
mergify[bot]
fde6f13b4b fix: cannot cancel jv after migrating from v12 (#29391) 2022-01-21 15:15:11 +05:30
marination
2185016f71 fix: (test) set user for price test 2022-01-21 14:55:43 +05:30
marination
33b5e1d7e6 fix: Sider and Linter 2022-01-21 14:49:10 +05:30
Saqib Ansari
4fec250cbf fix: merge conflicts 2022-01-21 14:33:59 +05:30
marination
c1dd3d7d9e tests: Variant without web item price fetch and add to cart
- Also, optionally get image from template web item for cart if variant has no image
2022-01-21 14:33:30 +05:30
Saqib Ansari
8e2b96e814 fix: failing test cases
(cherry picked from commit ae580d116c)

# Conflicts:
#	erpnext/assets/doctype/asset/test_asset.py
2022-01-21 08:56:40 +00:00
Saqib Ansari
398fab4786 fix: depreciation amount on selling an asset
(cherry picked from commit 0bbde4819b)
2022-01-21 08:56:40 +00:00
Saqib Ansari
844b73efcd fix: first depreciation amount in asset
(cherry picked from commit 60bfead773)
2022-01-21 08:56:39 +00:00
mergify[bot]
9869b31b31 fix(Timesheets): calculate to time (backport #28589) (#29390)
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Himanshu <himanshuwarekar@yahoo.com>
2022-01-21 13:51:49 +05:30
Deepesh Garg
0f83ba221f fix: first depreciation amount in asset 2022-01-21 12:43:09 +05:30
Saqib Ansari
04e3fb85e5 Merge pull request #28851 from frappe/mergify/bp/version-13-hotfix/pr-28507
fix: Clear Depreciation Schedule before modification (backport #28507)
2022-01-21 12:34:05 +05:30
Saqib Ansari
e623716391 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29373 2022-01-21 12:28:35 +05:30
Marica
1f099bcdfb Merge pull request #29077 from dj12djdjs/fix-ecommerce-cart-badge
fix(e-commerce)(UX): shopping cart qty badge
2022-01-21 12:28:28 +05:30
Saqib Ansari
b66f86b044 fix: merge conflict 2022-01-21 12:28:02 +05:30
Saqib Ansari
afda48a12b fix: cost center validation of asset
(cherry picked from commit 4390adcaa1)

# Conflicts:
#	erpnext/assets/doctype/asset/test_asset.py
2022-01-21 06:52:31 +00:00
mergify[bot]
ceba7f2fe4 fix: updated packed_items getting fetched on Sales Return / Credit Note (#28607) (#29387)
* fix: updated packed_items getting fetched on sales return

* fix: updated test cases for product bundle returns

* fix: moved calculate_mapped_packed_items_return to stock.utils

* fix: updated packed_items getting fetched on Return/Credit Note

* refactor: change var name to avoid `self`

* fix: check item code before assigning parent item

(cherry picked from commit 20216fa9f1)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-21 12:11:58 +05:30
Chillar Anand
696fc0a482 refactor: Added deprecation warning for hospitality domain (#29384) 2022-01-21 11:36:40 +05:30
marination
7d130af12b fix: Cart Logic of Item variant without Website Item 2022-01-20 19:34:36 +05:30
Anupam Kumar
29241070c1 Merge branch 'version-13-hotfix' into crm-contact-duplication-v13 2022-01-20 17:30:21 +05:30
Ankush Menat
d32f229483 ci: fail on merge conflict label 2022-01-20 15:39:32 +05:30
Ankush Menat
8a9a13cb96 ci: concurrency control on v13 branches 2022-01-20 15:39:32 +05:30
Ankush Menat
d190ea4ad3 ci: disable coverage on v13 branch 2022-01-20 15:39:32 +05:30
Marica
eb44b16e31 Merge pull request #29378 from frappe/mergify/bp/version-13-hotfix/pr-29375
fix: (enhance) BOM Operations Report (backport #29375)
2022-01-20 15:34:20 +05:30
mergify[bot]
b9a42807f8 fix: show stock UOM for material transfers (#29376) (#29377)
(cherry picked from commit 7e407bcc42)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-20 15:06:11 +05:30
marination
655d8980dc fix: Allow only BOM with ops in BOM ID filter & add total row to report
(cherry picked from commit af734298c8)
2022-01-20 09:25:05 +00:00
marination
e963d0e8e0 fix: Sider (unused imports) and Linter(add extra empty line)
(cherry picked from commit 3c7b772112)
2022-01-20 09:25:05 +00:00
marination
77cb7ebd40 fix: (enhance) BOM Operations Report
- Added filters in the Report for BOM ID, Item Code and Workstation
- Converted Raw SQL to frappe.qb and added method to get filtered data
- Changed fieldtype of 'Time in mins' from Int to Float
- Get BOM wise grouped data to  keep order and accurate grouping in report

(cherry picked from commit a9ff1fc52e)
2022-01-20 09:25:04 +00:00
Deepesh Garg
829ea71ae1 Merge pull request #29366 from deepeshgarg007/bank_statement_import_template
fix: Cleanup empty rows on bank statement import
2022-01-20 11:24:48 +05:30
Deepesh Garg
25398d017b fix: Cleanup empty rows on bank statement import 2022-01-20 10:51:59 +05:30
mergify[bot]
f75d36dcb4 fix: from time and to time not updated in drag and drop action #29114 (#29361)
fix: from time and to time not updated in drag and drop action
(cherry picked from commit 8b5827ed6d)

Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com>
2022-01-19 19:30:21 +05:30
Deepesh Garg
e5898a37dc chore: Add test case 2022-01-19 16:43:38 +05:30
Deepesh Garg
f4b3e208b7 feat: Splitting group assets 2022-01-19 16:43:36 +05:30
Marica
6c45223606 Merge pull request #29352 from frappe/mergify/bp/version-13-hotfix/pr-28843
refactor: update_serial_no function for old Maintenance Visits (backport #28843)
2022-01-19 14:48:24 +05:30
marination
93d665b38a fix: Patch (reload doc) 2022-01-19 13:53:18 +05:30
Anupam
44d2098530 fix: changed function name 2022-01-19 13:10:23 +05:30
marination
3023757a60 fix: Conflicts 2022-01-19 13:06:52 +05:30
Noah Jacob
add36363ab fix: Serial No. filters for unscheduled visits
(cherry picked from commit dec7513773)
2022-01-19 07:19:51 +00:00
Noah Jacob
93325c72f0 refactor: moved purpose table mandatory check to server
(cherry picked from commit b98c61f2a7)

# Conflicts:
#	erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
2022-01-19 07:19:50 +00:00
Noah Jacob
0bb0a9d032 fix(test): fixed test case not creating serials
(cherry picked from commit 90d32006f4)
2022-01-19 07:19:49 +00:00
Noah Jacob
f541b5c780 test: added test for serial no. filters
(cherry picked from commit 9109660a69)
2022-01-19 07:19:49 +00:00
Noah Jacob
dc0d974144 fix(patch): updates maintenance schedule field for old docs
(cherry picked from commit c872aa43f8)

# Conflicts:
#	erpnext/patches.txt
2022-01-19 07:19:48 +00:00
Noah Jacob
8775154fa0 refactor: update_serial_no function and code cleanup
(cherry picked from commit 4f52b86d7e)

# Conflicts:
#	erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
2022-01-19 07:19:47 +00:00
Anupam Kumar
b917f33399 Merge branch 'version-13-hotfix' into crm-contact-duplication-v13 2022-01-18 16:38:04 +05:30
Anupam
79ce36c19f fix: sider issue 2022-01-18 16:36:40 +05:30
Anupam
c8eae9dcef fix: contact duplication on converting lead to customer 2022-01-18 16:14:45 +05:30
mergify[bot]
69fded250c fix: enable allow negative stock by default in reposts (#29331) (#29333)
Negative stock validation is done BEFORE reposting as such it's not
required to be performed again and most likely this fails on old
intermediate data where user has no clue why it failed.

(cherry picked from commit fabe0bce15)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-18 13:33:29 +05:30
mergify[bot]
d7f16c4924 refactor: convert CTE validation to python (#29324)
Some old servers running on mariadb <10.2 can't work with CTEs hence
reverting to basic python code

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-01-18 11:17:32 +05:30
Devin Slauenwhite
c121ab8ef4 Merge remote-tracking branch 'upstream/version-13-hotfix' into fix-ecommerce-cart-badge 2022-01-17 09:15:14 -05:00
Sagar Sharma
8274467d6b Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount 2022-01-15 11:53:42 +05:30
Sagar Sharma
0a9ec9f591 refactor: use frappe.qb instead of sql 2022-01-14 19:22:43 +05:30
Sagar Sharma
fc65a3d989 feat: add patch 2022-01-14 16:15:26 +05:30
Deepesh Garg
eeb3d72dc0 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-01-14 11:43:58 +05:30
Sagar Sharma
5de6b8dc4d fix: check so_detail before dn_detail 2022-01-12 16:13:06 +05:30
Sagar Sharma
63aaa1e357 fix: consider returned_qty while updating billed_amt 2022-01-12 16:10:57 +05:30
Ganga Manoj
852543cbcd fix: Merge conflicts 2022-01-12 14:05:09 +05:30
Devin Slauenwhite
d763b16e9f Merge remote-tracking branch 'upstream/version-13-hotfix' into fix-ecommerce-cart-badge 2021-12-31 10:41:37 -05:00
Devin Slauenwhite
c9a9d0bab8 fix(ux): tripple digit cart qty 2021-12-29 21:47:04 -05:00
Deepesh Garg
29ca1950a2 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-29 18:25:52 +05:30
Deepesh Garg
8a4de134ac Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-14 11:38:46 +05:30
Deepesh Garg
69c9856bee Merge branch 'enterprise-hotfix' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-14 11:37:21 +05:30
GangaManoj
d000782de0 fix: Edit dates and frequency of depreciation
(cherry picked from commit 4311936b7d)
2021-12-13 16:20:51 +00:00
GangaManoj
855ae399b5 fix: Test if clear_depreciation_schedule() works for multiple finance books
(cherry picked from commit 6ec5a19063)
2021-12-13 16:20:50 +00:00
GangaManoj
ae875ab021 fix: Retain depreciation schedule rows that are linked with JEs while clearing the schedule
(cherry picked from commit 33a0b1db2c)
2021-12-13 16:20:49 +00:00
GangaManoj
57f41bc30a fix: Test if multiple Depreciation Schedules are set up for multiple Finance Books
(cherry picked from commit f455de2924)
2021-12-13 16:20:49 +00:00
GangaManoj
8a4019e501 fix: Clear Depreciation Schedule entries that aren't linked with Journal Entries before modifying the schedule
(cherry picked from commit 475d8394e4)
2021-12-13 16:20:48 +00:00
GangaManoj
54cac351cd fix: Rename loop variable
(cherry picked from commit 059d1f3b74)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py
2021-12-13 16:20:47 +00:00
GangaManoj
db3d2c31a3 fix: Prevent clearing of Depreciation Schedule on adding more than one Finance Book
(cherry picked from commit a4043c035d)
2021-12-13 16:20:43 +00:00
Saqib
4343bdd8db fix: cannot load company form (#28536) 2021-11-24 11:04:15 +05:30
Saqib
2bf30db3d1 fix: Add indexes in stock queries (#27888) 2021-10-11 10:27:23 +05:30
Deepesh Garg
8b1be1db4c fix: Incorrect actual qty updation 2021-10-11 09:54:35 +05:30
Deepesh Garg
13d501c0b9 fix: Remove print statements 2021-10-11 09:54:33 +05:30
Deepesh Garg
59ae7e09cb fix: Remove unwanted comments 2021-10-11 09:54:14 +05:30
Deepesh Garg
4425442bbb fix: Optimization on Bin updation 2021-10-11 09:53:55 +05:30
Deepesh Garg
ed98125807 fix: Add indexes in stock queries 2021-10-11 09:53:44 +05:30
Deepesh Garg
5aae91e6cb Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-10-11 09:49:47 +05:30
mergify[bot]
c6725d43cb fix: cannot set custom label for 'total' field in print format (#27666) 2021-09-27 14:29:54 +05:30
Subin Tom
f36ed34907 fix: Tax Breakup table headers fix 2021-09-20 16:16:35 +05:30
Deepesh Garg
ccf1472c56 fix: Update modified timestamp in selling settings 2021-09-20 14:14:59 +05:30
Subin Tom
bb95287357 fix: tax breakup test fix, eway bill hsn fix 2021-09-20 13:01:50 +05:30
Subin Tom
35d6c6ade4 fix: added gst fields,warehouse validation to pos inv,patch 2021-09-20 13:01:49 +05:30
Subin Tom
f9c46e766b fix: Tax breakup based on items 2021-09-20 13:00:33 +05:30
Deepesh Garg
1c183c16d4 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-20 12:57:46 +05:30
Saqib
a4140a1487 fix: fieldname 2021-09-14 13:50:51 +05:30
Deepesh Garg
3c20553104 fix: Linting Issues 2021-09-14 13:18:42 +05:30
Deepesh Garg
b169558885 feat: Merge POS invoices based on customer group 2021-09-14 13:18:24 +05:30
Deepesh Garg
21d2efbbdc fix: Add missing imports 2021-09-09 11:52:30 +05:30
Deepesh Garg
591b57a02a fix: Add missing imports 2021-09-09 11:22:04 +05:30
Deepesh Garg
be55fb31e4 fix: Autoname for customer and supplier 2021-09-09 10:27:06 +05:30
Deepesh Garg
0419ba1d78 Merge branch 'enterprise-hotfix' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-07 18:15:01 +05:30
Deepesh Garg
945214771a Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-07 18:14:01 +05:30
Deepesh Garg
0e10f69c6e Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-08-23 23:00:54 +05:30
Deepesh Garg
3d039d0fac fix: Store records to delete in a separate list 2021-08-12 18:19:21 +05:30
Deepesh Garg
b74db6f2b8 fix: Cascade deletion for Company 2021-08-12 17:53:27 +05:30
Deepesh Garg
efc76f4245 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-08-12 17:46:44 +05:30
Deepesh Garg
653e1455ed fix: Use update flag for company dependant fixtures 2021-07-13 20:02:38 +05:30
Deepesh Garg
ac5e7f2b74 fix: Error on creation of company for India 2021-07-13 20:01:36 +05:30
1732 changed files with 107337 additions and 67174 deletions

View File

@@ -28,6 +28,9 @@ ignore =
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
max-line-length = 200
exclude=.github/helper/semgrep_rules

View File

@@ -17,3 +17,6 @@ f0bcb753fb7ebbb64bb0d6906d431d002f0f7d8f
# imports cleanup
4b2be2999f2203493b49bf74c5b440d49e38b5e3
# formatting with black
c07713b860505211db2af685e2e950bf5dd7dd3a

View File

@@ -8,6 +8,10 @@ on:
workflow_dispatch:
concurrency:
group: patch-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
@@ -31,7 +35,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.6
python-version: 3.7
- name: Setup Node
uses: actions/setup-node@v2

View File

@@ -12,6 +12,10 @@ on:
- '**.js'
- '**.md'
concurrency:
group: server-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
@@ -89,39 +93,8 @@ jobs:
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Upload Coverage Data
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
pip3 install coverage==5.5
pip3 install coveralls==3.0.1
coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
COVERALLS_PARALLEL: true
coveralls:
name: Coverage Wrap Up
needs: test
container: python:3-slim
runs-on: ubuntu-18.04
steps:
- name: Clone
uses: actions/checkout@v2
- name: Coveralls Finished
run: |
cd ${GITHUB_WORKSPACE}
pip3 install coverage==5.5
pip3 install coveralls==3.0.1
coveralls --finish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -6,6 +6,10 @@ on:
- '**.md'
workflow_dispatch:
concurrency:
group: ui-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04

View File

@@ -26,12 +26,19 @@ repos:
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
hooks:
- id: isort
exclude: ".*setup.py$"
ci:
autoupdate_schedule: weekly
skip: []

View File

@@ -1,53 +1,59 @@
import inspect
import frappe
from erpnext.hooks import regional_overrides
__version__ = '13.19.0'
__version__ = "13.26.0"
def get_default_company(user=None):
'''Get default company for user'''
"""Get default company for user"""
from frappe.defaults import get_user_default_as_list
if not user:
user = frappe.session.user
companies = get_user_default_as_list(user, 'company')
companies = get_user_default_as_list(user, "company")
if companies:
default_company = companies[0]
else:
default_company = frappe.db.get_single_value('Global Defaults', 'default_company')
default_company = frappe.db.get_single_value("Global Defaults", "default_company")
return default_company
def get_default_currency():
'''Returns the currency of the default company'''
"""Returns the currency of the default company"""
company = get_default_company()
if company:
return frappe.get_cached_value('Company', company, 'default_currency')
return frappe.get_cached_value("Company", company, "default_currency")
def get_default_cost_center(company):
'''Returns the default cost center of the company'''
"""Returns the default cost center of the company"""
if not company:
return None
if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value('Company', company, 'cost_center')
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company]
def get_company_currency(company):
'''Returns the default company currency'''
"""Returns the default company currency"""
if not frappe.flags.company_currency:
frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency', cache=True)
frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True
)
return frappe.flags.company_currency[company]
def set_perpetual_inventory(enable=1, company=None):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
@@ -56,9 +62,10 @@ def set_perpetual_inventory(enable=1, company=None):
company.enable_perpetual_inventory = enable
company.save()
def encode_company_abbr(name, company=None, abbr=None):
'''Returns name encoded with company abbreviation'''
company_abbr = abbr or frappe.get_cached_value('Company', company, "abbr")
"""Returns name encoded with company abbreviation"""
company_abbr = abbr or frappe.get_cached_value("Company", company, "abbr")
parts = name.rsplit(" - ", 1)
if parts[-1].lower() != company_abbr.lower():
@@ -66,65 +73,73 @@ def encode_company_abbr(name, company=None, abbr=None):
return " - ".join(parts)
def is_perpetual_inventory_enabled(company):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
if not hasattr(frappe.local, 'enable_perpetual_inventory'):
if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
company, "enable_perpetual_inventory") or 0
frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
)
return frappe.local.enable_perpetual_inventory[company]
def get_default_finance_book(company=None):
if not company:
company = get_default_company()
if not hasattr(frappe.local, 'default_finance_book'):
if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
company, "default_finance_book")
frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book"
)
return frappe.local.default_finance_book[company]
def get_party_account_type(party_type):
if not hasattr(frappe.local, 'party_account_types'):
if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = frappe.db.get_value("Party Type",
party_type, "account_type") or ''
frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or ""
)
return frappe.local.party_account_types[party_type]
def get_region(company=None):
'''Return the default country based on flag, company or global settings
"""Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company`
'''
"""
if company or frappe.flags.company:
return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country')
return frappe.get_cached_value("Company", company or frappe.flags.company, "country")
elif frappe.flags.country:
return frappe.flags.country
else:
return frappe.get_system_settings('country')
return frappe.get_system_settings("country")
def allow_regional(fn):
'''Decorator to make a function regionally overridable
"""Decorator to make a function regionally overridable
Example:
@erpnext.allow_regional
def myfunction():
pass'''
pass"""
def caller(*args, **kwargs):
region = get_region()
fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
fn_name = inspect.getmodule(fn).__name__ + "." + fn.__name__
if region in regional_overrides and fn_name in regional_overrides[region]:
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else:
@@ -132,10 +147,16 @@ def allow_regional(fn):
return caller
def get_last_membership(member):
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=member, paid=1), order_by='to_date desc', limit=1)
"""Returns last membership if exists"""
last_membership = frappe.get_all(
"Membership",
"name,to_date,membership_type",
dict(member=member, paid=1),
order_by="to_date desc",
limit=1,
)
if last_membership:
return last_membership[0]

View File

@@ -23,32 +23,31 @@ class ERPNextAddress(Address):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"))
frappe.throw(
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"),
)
def on_update(self):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
address_display = get_address_display(self.as_dict())
filters = {
"customer_primary_address": self.name
}
filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
@frappe.whitelist()
def get_shipping_address(company, address = None):
def get_shipping_address(company, address=None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
["Address", "is_your_company_address", "=", 1]
["Address", "is_your_company_address", "=", 1],
]
fields = ["*"]
if address and frappe.db.get_value('Dynamic Link',
{'parent': address, 'link_name': company}):
if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}):
filters.append(["Address", "name", "=", address])
if not address:
filters.append(["Address", "is_shipping_address", "=", 1])

View File

@@ -12,15 +12,24 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
to_date = None, timespan = None, time_interval = None, heatmap_year = None):
def get(
chart_name=None,
chart=None,
no_cache=None,
filters=None,
from_date=None,
to_date=None,
timespan=None,
time_interval=None,
heatmap_year=None,
):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name)
chart = frappe.get_doc("Dashboard Chart", chart_name)
else:
chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan
if chart.timespan == 'Select Date Range':
if chart.timespan == "Select Date Range":
from_date = chart.from_date
to_date = chart.to_date
@@ -31,17 +40,23 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
company = filters.get("company")
if not account and chart_name:
frappe.throw(_("Account is not set for the dashboard chart {0}")
.format(get_link_to_form("Dashboard Chart", chart_name)))
frappe.throw(
_("Account is not set for the dashboard chart {0}").format(
get_link_to_form("Dashboard Chart", chart_name)
)
)
if not frappe.db.exists("Account", account) and chart_name:
frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
.format(account, get_link_to_form("Dashboard Chart", chart_name)))
frappe.throw(
_("Account {0} does not exists in the dashboard chart {1}").format(
account, get_link_to_form("Dashboard Chart", chart_name)
)
)
if not to_date:
to_date = nowdate()
if not from_date:
if timegrain in ('Monthly', 'Quarterly'):
if timegrain in ("Monthly", "Quarterly"):
from_date = get_from_date_from_timespan(to_date, timespan)
# fetch dates to plot
@@ -54,16 +69,14 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
result = build_result(account, dates, gl_entries)
return {
"labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
"datasets": [{
"name": account,
"values": [r[1] for r in result]
}]
"labels": [formatdate(r[0].strftime("%Y-%m-%d")) for r in result],
"datasets": [{"name": account, "values": [r[1] for r in result]}],
}
def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates]
root_type = frappe.db.get_value('Account', account, 'root_type')
root_type = frappe.db.get_value("Account", account, "root_type")
# start with the first date
date_index = 0
@@ -78,30 +91,34 @@ def build_result(account, dates, gl_entries):
result[date_index][1] += entry.debit - entry.credit
# if account type is credit, switch balances
if root_type not in ('Asset', 'Expense'):
if root_type not in ("Asset", "Expense"):
for r in result:
r[1] = -1 * r[1]
# for balance sheet accounts, the totals are cumulative
if root_type in ('Asset', 'Liability', 'Equity'):
if root_type in ("Asset", "Liability", "Equity"):
for i, r in enumerate(result):
if i > 0:
r[1] = r[1] + result[i-1][1]
r[1] = r[1] + result[i - 1][1]
return result
def get_gl_entries(account, to_date):
child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
child_accounts = get_descendants_of("Account", account, ignore_permissions=True)
child_accounts.append(account)
return frappe.db.get_all('GL Entry',
fields = ['posting_date', 'debit', 'credit'],
filters = [
dict(posting_date = ('<', to_date)),
dict(account = ('in', child_accounts)),
dict(voucher_type = ('!=', 'Period Closing Voucher'))
return frappe.db.get_all(
"GL Entry",
fields=["posting_date", "debit", "credit"],
filters=[
dict(posting_date=("<", to_date)),
dict(account=("in", child_accounts)),
dict(voucher_type=("!=", "Period Closing Voucher")),
],
order_by = 'posting_date asc')
order_by="posting_date asc",
)
def get_dates_from_timegrain(from_date, to_date, timegrain):
days = months = years = 0
@@ -116,6 +133,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date):
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
date = get_period_ending(
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
dates.append(date)
return dates

View File

@@ -1,4 +1,3 @@
import frappe
from frappe import _
from frappe.email import sendmail_to_system_managers
@@ -23,20 +22,23 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
"""Validates service_stop_date for Purchase Invoice and Sales Invoice"""
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {}
old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
{"parent": doc.name}, ["name", "service_stop_date"])
old_doc = frappe.db.get_all(
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
)
for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or ""
for item in doc.items:
if not item.get(enable_check): continue
if not item.get(enable_check):
continue
if item.service_stop_date:
if date_diff(item.service_stop_date, item.service_start_date) < 0:
@@ -45,21 +47,31 @@ def validate_service_stop_date(doc):
if date_diff(item.service_stop_date, item.service_end_date) > 0:
frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
if (
old_stop_dates
and old_stop_dates.get(item.name)
and item.service_stop_date != old_stop_dates.get(item.name)
):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def build_conditions(process_type, account, company):
conditions=''
deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account"
conditions = ""
deferred_account = (
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
)
if account:
conditions += "AND %s='%s'"%(deferred_account, account)
conditions += "AND %s='%s'" % (deferred_account, account)
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''):
def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -68,14 +80,19 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
end_date = add_days(today(), -1)
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
invoices = frappe.db.sql_list(
"""
select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
""".format(
conditions
),
(end_date, start_date),
) # nosec
# For each invoice, book deferred expense
for invoice in invoices:
@@ -85,7 +102,10 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -94,14 +114,19 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
end_date = add_days(today(), -1)
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
invoices = frappe.db.sql_list(
"""
select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
""".format(
conditions
),
(end_date, start_date),
) # nosec
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
@@ -110,30 +135,43 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None):
if not posting_date:
posting_date = add_days(today(), -1)
last_gl_entry = False
deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
deferred_account = (
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
)
prev_gl_entry = frappe.db.sql('''
prev_gl_entry = frappe.db.sql(
"""
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
prev_gl_via_je = frappe.db.sql('''
prev_gl_via_je = frappe.db.sql(
"""
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if prev_gl_via_je:
if (not prev_gl_entry) or (prev_gl_entry and
prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry:
@@ -157,66 +195,94 @@ def get_booking_dates(doc, item, posting_date=None):
else:
return None, None, None
def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
def calculate_monthly_amount(
doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
):
amount, base_amount = 0, 0
if not last_gl_entry:
total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
(item.service_end_date.month - item.service_start_date.month) + 1
total_months = (
(item.service_end_date.year - item.service_start_date.year) * 12
+ (item.service_end_date.month - item.service_start_date.month)
+ 1
)
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
/ flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt(
date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
)
actual_months = rounded(total_months * prorate_factor, 1)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount
if account_currency==doc.company_currency:
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
partial_month = flt(date_diff(end_date, start_date)) \
/ flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date))
)
base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
amount, base_amount = 0, 0
if not last_gl_entry:
base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
if account_currency==doc.company_currency:
base_amount = flt(
item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency:
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount
def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
@@ -225,20 +291,31 @@ def get_already_booked_amount(doc, item):
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
gl_entries_details = frappe.db.sql('''
gl_entries_details = frappe.db.sql(
"""
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
group by voucher_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
""".format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
journal_entry_details = frappe.db.sql('''
journal_entry_details = frappe.db.sql(
"""
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
""".format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
@@ -246,20 +323,29 @@ def get_already_booked_amount(doc, item):
if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount
else:
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
already_booked_amount_in_account_currency = (
gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
)
already_booked_amount_in_account_currency += (
journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
)
return already_booked_amount, already_booked_amount_in_account_currency
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
def _book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
if not (start_date and end_date):
return
account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice":
@@ -272,107 +358,179 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1
if book_deferred_entries_based_on == 'Months':
amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
start_date, end_date, total_days, total_booking_days, account_currency)
if book_deferred_entries_based_on == "Months":
amount, base_amount = calculate_monthly_amount(
doc,
item,
last_gl_entry,
start_date,
end_date,
total_days,
total_booking_days,
account_currency,
)
else:
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
total_days, total_booking_days, account_currency)
amount, base_amount = calculate_amount(
doc, item, last_gl_entry, total_days, total_booking_days, account_currency
)
if not amount:
return
# check if books nor frozen till endate:
if getdate(end_date) >= getdate(accounts_frozen_upto):
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
end_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
end_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
return
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
_book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
)
submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on"
)
for item in doc.get('items'):
for item in doc.get("items"):
if item.get(enable_check):
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
_book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
def process_deferred_accounting(posting_date=None):
''' Converts deferred income/expense into income/expense
Executed via background jobs on every month end '''
"""Converts deferred income/expense into income/expense
Executed via background jobs on every month end"""
if not posting_date:
posting_date = today()
if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
if not cint(
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
):
return
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
companies = frappe.get_all('Company')
companies = frappe.get_all("Company")
for company in companies:
for record_type in ('Income', 'Expense'):
doc = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
company=company.name,
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type
))
for record_type in ("Income", "Expense"):
doc = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
company=company.name,
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type,
)
)
doc.insert()
doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
def make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
if amount == 0: return
if amount == 0:
return
gl_entries = []
gl_entries.append(
doc.get_gl_dict({
"account": credit_account,
"against": against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency, item=item)
doc.get_gl_dict(
{
"account": credit_account,
"against": against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
"posting_date": posting_date,
"project": project,
"against_voucher_type": "Process Deferred Accounting",
"against_voucher": deferred_process,
},
account_currency,
item=item,
)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
doc.get_gl_dict({
"account": debit_account,
"against": against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency, item=item)
doc.get_gl_dict(
{
"account": debit_account,
"against": against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
"posting_date": posting_date,
"project": project,
"against_voucher_type": "Process Deferred Accounting",
"against_voucher": deferred_process,
},
account_currency,
item=item,
)
)
if gl_entries:
@@ -382,68 +540,88 @@ def make_gl_entries(doc, credit_account, debit_account, against,
except Exception as e:
if frappe.flags.in_test:
traceback = frappe.get_traceback()
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
raise e
else:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
link = get_link_to_form('Process Deferred Accounting', deferred_process)
link = get_link_to_form("Process Deferred Accounting", deferred_process)
content = _("Deferred accounting failed for some invoices:") + "\n"
content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
content += _(
"Please check Process Deferred Accounting {0} and submit manually after resolving errors."
).format(link)
sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item,
deferred_process=None, submit='No'):
if amount == 0: return
def book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
submit="No",
):
journal_entry = frappe.new_doc('Journal Entry')
if amount == 0:
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
else 'Deferred Expense'
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
'account': credit_account,
'credit': base_amount,
'credit_in_account_currency': amount,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
"account": credit_account,
"credit": base_amount,
"credit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
credit_entry = {
'account': debit_account,
'debit': base_amount,
'debit_in_account_currency': amount,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
"account": debit_account,
"debit": base_amount,
"debit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
for dimension in get_accounting_dimensions():
debit_entry.update({
dimension: item.get(dimension)
})
debit_entry.update({dimension: item.get(dimension)})
credit_entry.update({
dimension: item.get(dimension)
})
credit_entry.update({dimension: item.get(dimension)})
journal_entry.append('accounts', debit_entry)
journal_entry.append('accounts', credit_entry)
journal_entry.append("accounts", debit_entry)
journal_entry.append("accounts", credit_entry)
try:
journal_entry.save()
@@ -455,20 +633,30 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == 'Sales Invoice':
credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
['income_account', 'deferred_revenue_account'])
if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item",
{"name": voucher_detail_no},
["income_account", "deferred_revenue_account"],
)
else:
credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
['deferred_expense_account', 'expense_account'])
credit_account, debit_account = frappe.db.get_value(
"Purchase Invoice Item",
{"name": voucher_detail_no},
["deferred_expense_account", "expense_account"],
)
if dr_or_cr == 'Debit':
if dr_or_cr == "Debit":
return debit_account
else:
return credit_account

View File

@@ -10,11 +10,17 @@ from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_
import erpnext
class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass
class RootNotEditable(frappe.ValidationError):
pass
class BalanceMismatchError(frappe.ValidationError):
pass
class Account(NestedSet):
nsm_parent_field = 'parent_account'
nsm_parent_field = "parent_account"
def on_update(self):
if frappe.local.flags.ignore_update_nsm:
return
@@ -22,17 +28,20 @@ class Account(NestedSet):
super(Account, self).on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
"frozen_accounts_modifier")
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier"
)
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True)
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
def validate(self):
from erpnext.accounts.utils import validate_field_number
if frappe.local.flags.allow_unverified_charts:
return
self.validate_parent()
@@ -49,22 +58,33 @@ class Account(NestedSet):
def validate_parent(self):
"""Fetch Parent Details and validate parent account"""
if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account,
["name", "is_group", "company"], as_dict=1)
par = frappe.db.get_value(
"Account", self.parent_account, ["name", "is_group", "company"], as_dict=1
)
if not par:
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
throw(
_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)
)
elif par.name == self.name:
throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
elif not par.is_group:
throw(_("Account {0}: Parent account {1} can not be a ledger").format(self.name, self.parent_account))
throw(
_("Account {0}: Parent account {1} can not be a ledger").format(
self.name, self.parent_account
)
)
elif par.company != self.company:
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
.format(self.name, self.parent_account, self.company))
throw(
_("Account {0}: Parent account {1} does not belong to company: {2}").format(
self.name, self.parent_account, self.company
)
)
def set_root_and_report_type(self):
if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account,
["report_type", "root_type"], as_dict=1)
par = frappe.db.get_value(
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1
)
if par.report_type:
self.report_type = par.report_type
@@ -75,15 +95,20 @@ class Account(NestedSet):
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value:
if self.report_type != db_value.report_type:
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
(self.report_type, self.lft, self.rgt))
frappe.db.sql(
"update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
(self.report_type, self.lft, self.rgt),
)
if self.root_type != db_value.root_type:
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
(self.root_type, self.lft, self.rgt))
frappe.db.sql(
"update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
(self.root_type, self.lft, self.rgt),
)
if self.root_type and not self.report_type:
self.report_type = "Balance Sheet" \
if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
self.report_type = (
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
)
def validate_root_details(self):
# does not exists parent
@@ -96,21 +121,26 @@ class Account(NestedSet):
def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
if (
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
return
ancestors = get_root_company(self.company)
if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return
if not frappe.db.get_value("Account",
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
if not frappe.db.get_value(
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name"
):
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account:
descendants = get_descendants_of('Company', self.company)
if not descendants: return
descendants = get_descendants_of("Company", self.company)
if not descendants:
return
parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
["account_name", "account_number"])
parent_acc_name, parent_acc_number = frappe.db.get_value(
"Account", self.parent_account, ["account_name", "account_number"]
)
filters = {
"company": ["in", descendants],
"account_name": parent_acc_name,
@@ -118,10 +148,13 @@ class Account(NestedSet):
if parent_acc_number:
filters["account_number"] = parent_acc_number
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: return
if not parent_acc_name_map:
return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
@@ -142,26 +175,38 @@ class Account(NestedSet):
def validate_frozen_accounts_modifier(self):
old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if old_value and old_value != self.freeze_account:
frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
if not frozen_accounts_modifier or \
frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", None, "frozen_accounts_modifier"
)
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self):
from erpnext.accounts.utils import get_balance_on
if not self.get("__islocal") and self.balance_must_be:
account_balance = get_balance_on(self.name)
if account_balance > 0 and self.balance_must_be == "Credit":
frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
frappe.throw(
_(
"Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"
)
)
elif account_balance < 0 and self.balance_must_be == "Debit":
frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
frappe.throw(
_(
"Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"
)
)
def validate_account_currency(self):
if not self.account_currency:
self.account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
@@ -170,45 +215,52 @@ class Account(NestedSet):
company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
frappe.throw(
_(
"While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA"
).format(company_bold, parent_acc_name_bold),
title=_("Account Not Found"),
)
# validate if parent of child company account to be added is a group
if (frappe.db.get_value("Account", self.parent_account, "is_group")
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value(
"Account", parent_acc_name_map[company], "is_group"
):
msg = _(
"While creating account for Child Company {0}, parent account {1} found as a ledger account."
).format(company_bold, parent_acc_name_bold)
msg += "<br><br>"
msg += _("Please convert the parent account in corresponding child company to a group account.")
msg += _(
"Please convert the parent account in corresponding child company to a group account."
)
frappe.throw(msg, title=_("Invalid Parent Account"))
filters = {
"account_name": self.account_name,
"company": company
}
filters = {"account_name": self.account_name, "company": company}
if self.account_number:
filters["account_number"] = self.account_number
child_account = frappe.db.get_value("Account", filters, 'name')
child_account = frappe.db.get_value("Account", filters, "name")
if not child_account:
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True
doc.update({
"company": company,
# parent account's currency should be passed down to child account's curreny
# if it is None, it picks it up from default company currency, which might be unintended
"account_currency": erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company]
})
doc.update(
{
"company": company,
# parent account's currency should be passed down to child account's curreny
# if it is None, it picks it up from default company currency, which might be unintended
"account_currency": erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company],
}
)
doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company))
frappe.msgprint(_("Account {0} is added in the child company {1}").format(doc.name, company))
elif child_account:
# update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account)
parent_value_changed = False
for field in ['account_type', 'freeze_account', 'balance_must_be']:
for field in ["account_type", "freeze_account", "balance_must_be"]:
if doc.get(field) != self.get(field):
parent_value_changed = True
doc.set(field, self.get(field))
@@ -243,8 +295,11 @@ class Account(NestedSet):
return frappe.db.get_value("GL Entry", {"account": self.name})
def check_if_child_exists(self):
return frappe.db.sql("""select name from `tabAccount` where parent_account = %s
and docstatus != 2""", self.name)
return frappe.db.sql(
"""select name from `tabAccount` where parent_account = %s
and docstatus != 2""",
self.name,
)
def validate_mandatory(self):
if not self.root_type:
@@ -260,73 +315,99 @@ class Account(NestedSet):
super(Account, self).on_trash(True)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name from tabAccount
return frappe.db.sql(
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
and %s like %s order by name limit %s, %s"""
% ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, start, page_len),
as_list=1,
)
def get_account_currency(account):
"""Helper function to get account currency"""
if not account:
return
def generator():
account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"])
account_currency, company = frappe.get_cached_value(
"Account", account, ["account_currency", "company"]
)
if not account_currency:
account_currency = frappe.get_cached_value('Company', company, "default_currency")
account_currency = frappe.get_cached_value("Company", company, "default_currency")
return account_currency
return frappe.local_cache("account_currency", account, generator)
def on_doctype_update():
frappe.db.add_index("Account", ["lft", "rgt"])
def get_account_autoname(account_number, account_name, company):
# first validate if company exists
company = frappe.get_cached_value('Company', company, ["abbr", "name"], as_dict=True)
company = frappe.get_cached_value("Company", company, ["abbr", "name"], as_dict=True)
if not company:
frappe.throw(_('Company {0} does not exist').format(company))
frappe.throw(_("Company {0} does not exist").format(company))
parts = [account_name.strip(), company.abbr]
if cstr(account_number).strip():
parts.insert(0, cstr(account_number).strip())
return ' - '.join(parts)
return " - ".join(parts)
def validate_account_number(name, account_number, company):
if account_number:
account_with_same_number = frappe.db.get_value("Account",
{"account_number": account_number, "company": company, "name": ["!=", name]})
account_with_same_number = frappe.db.get_value(
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
)
if account_with_same_number:
frappe.throw(_("Account Number {0} already used in account {1}")
.format(account_number, account_with_same_number))
frappe.throw(
_("Account Number {0} already used in account {1}").format(
account_number, account_with_same_number
)
)
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account: return
if not account:
return
old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
["account_name", "account_number"])
old_acc_name, old_acc_number = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
allow_independent_account_creation = frappe.get_value(
"Company", account.company, "allow_account_creation_against_child_company"
)
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
message = _("Account {0} exists in parent company {1}.").format(
frappe.bold(old_acc_name), frappe.bold(ancestor)
)
message += "<br>"
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(
frappe.bold(ancestor)
)
message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
message += _("To overrule this, enable '{0}' in company {1}").format(
allow_child_account_creation, frappe.bold(account.company)
)
frappe.throw(message, title=_("Rename Not Allowed"))
@@ -339,42 +420,53 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if not from_descendant:
# Update and rename in child company accounts as well
descendants = get_descendants_of('Company', account.company)
descendants = get_descendants_of("Company", account.company)
if descendants:
sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
sync_update_account_number_in_child(
descendants, old_acc_name, account_name, account_number, old_acc_number
)
new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1)
return new_name
@frappe.whitelist()
def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging
if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new))
val = list(frappe.db.get_value("Account", new,
["is_group", "root_type", "company"]))
val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"]))
if val != [cint(is_group), root_type, company]:
throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
throw(
_(
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
)
)
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
frappe.db.set_value("Account", new, "parent_account",
frappe.db.get_value("Account", old, "parent_account"))
frappe.db.set_value(
"Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account")
)
frappe.rename_doc("Account", old, new, merge=1, force=1)
return new
@frappe.whitelist()
def get_root_company(company):
# return the topmost company in the hierarchy
ancestors = get_ancestors_of('Company', company, "lft asc")
ancestors = get_ancestors_of("Company", company, "lft asc")
return [ancestors[0]] if ancestors else []
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
def sync_update_account_number_in_child(
descendants, old_acc_name, account_name, account_number=None, old_acc_number=None
):
filters = {
"company": ["in", descendants],
"account_name": old_acc_name,
@@ -382,5 +474,7 @@ def sync_update_account_number_in_child(descendants, old_acc_name, account_name,
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
update_account_number(d["name"], account_name, account_number, from_descendant=True)
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -11,7 +11,9 @@ from six import iteritems
from unidecode import unidecode
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None):
def create_charts(
company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
):
chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
@@ -21,30 +23,41 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
if root_account:
root_type = child.get("root_type")
if account_name not in ["account_name", "account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
if account_name not in [
"account_name",
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate(account_name,
account_number, accounts)
account_name, account_name_in_db = add_suffix_if_duplicate(
account_name, account_number, accounts
)
is_group = identify_is_group(child)
report_type = "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] \
else "Profit and Loss"
report_type = (
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
)
account = frappe.get_doc({
"doctype": "Account",
"account_name": child.get('account_name') if from_coa_importer else account_name,
"company": company,
"parent_account": parent,
"is_group": is_group,
"root_type": root_type,
"report_type": report_type,
"account_number": account_number,
"account_type": child.get("account_type"),
"account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
"tax_rate": child.get("tax_rate")
})
account = frappe.get_doc(
{
"doctype": "Account",
"account_name": child.get("account_name") if from_coa_importer else account_name,
"company": company,
"parent_account": parent,
"is_group": is_group,
"root_type": root_type,
"report_type": report_type,
"account_number": account_number,
"account_type": child.get("account_type"),
"account_currency": child.get("account_currency")
or frappe.db.get_value("Company", company, "default_currency"),
"tax_rate": child.get("tax_rate"),
}
)
if root_account or frappe.local.flags.allow_unverified_charts:
account.flags.ignore_mandatory = True
@@ -64,10 +77,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_update_nsm = False
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:
account_name_in_db = unidecode(" - ".join([account_number,
account_name.strip().lower()]))
account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()]))
else:
account_name_in_db = unidecode(account_name.strip().lower())
@@ -77,16 +90,21 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
return account_name, account_name_in_db
def identify_is_group(child):
if child.get("is_group"):
is_group = child.get("is_group")
elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])):
elif len(
set(child.keys())
- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
):
is_group = 1
else:
is_group = 0
return is_group
def get_chart(chart_template, existing_company=None):
chart = {}
if existing_company:
@@ -96,11 +114,13 @@ def get_chart(chart_template, existing_company=None):
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts,
)
return standard_chart_of_accounts.get()
elif chart_template == "Standard with Numbers":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts_with_account_number,
)
return standard_chart_of_accounts_with_account_number.get()
else:
folders = ("verified",)
@@ -116,6 +136,7 @@ def get_chart(chart_template, existing_company=None):
if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree")
@frappe.whitelist()
def get_charts_for_country(country, with_standard=False):
charts = []
@@ -123,9 +144,10 @@ def get_charts_for_country(country, with_standard=False):
def _get_chart_name(content):
if content:
content = json.loads(content)
if (content and content.get("disabled", "No") == "No") \
or frappe.local.flags.allow_unverified_charts:
charts.append(content["name"])
if (
content and content.get("disabled", "No") == "No"
) or frappe.local.flags.allow_unverified_charts:
charts.append(content["name"])
country_code = frappe.db.get_value("Country", country, "code")
if country_code:
@@ -152,11 +174,21 @@ def get_charts_for_country(country, with_standard=False):
def get_account_tree_from_existing_company(existing_company):
all_accounts = frappe.get_all('Account',
filters={'company': existing_company},
fields = ["name", "account_name", "parent_account", "account_type",
"is_group", "root_type", "tax_rate", "account_number"],
order_by="lft, rgt")
all_accounts = frappe.get_all(
"Account",
filters={"company": existing_company},
fields=[
"name",
"account_name",
"parent_account",
"account_type",
"is_group",
"root_type",
"tax_rate",
"account_number",
],
order_by="lft, rgt",
)
account_tree = {}
@@ -165,6 +197,7 @@ def get_account_tree_from_existing_company(existing_company):
build_account_tree(account_tree, None, all_accounts)
return account_tree
def build_account_tree(tree, parent, all_accounts):
# find children
parent_account = parent.name if parent else ""
@@ -193,27 +226,29 @@ def build_account_tree(tree, parent, all_accounts):
# call recursively to build a subtree for current account
build_account_tree(tree[child.account_name], child, all_accounts)
@frappe.whitelist()
def validate_bank_account(coa, bank_account):
accounts = []
chart = get_chart(coa)
if chart:
def _get_account_names(account_master):
for account_name, child in iteritems(account_master):
if account_name not in ["account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
accounts.append(account_name)
_get_account_names(child)
_get_account_names(chart)
return (bank_account in accounts)
return bank_account in accounts
@frappe.whitelist()
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
''' get chart template from its folder and parse the json to be rendered as tree '''
"""get chart template from its folder and parse the json to be rendered as tree"""
chart = chart_data or get_chart(chart_template)
# if no template selected, return as it is
@@ -221,22 +256,33 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
return
accounts = []
def _import_accounts(children, parent):
''' recursively called to form a parent-child based list of dict from chart template '''
"""recursively called to form a parent-child based list of dict from chart template"""
for account_name, child in iteritems(children):
account = {}
if account_name in ["account_name", "account_number", "account_type",\
"root_type", "is_group", "tax_rate"]: continue
if account_name in [
"account_name",
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
continue
if from_coa_importer:
account_name = child['account_name']
account_name = child["account_name"]
account['parent_account'] = parent
account['expandable'] = True if identify_is_group(child) else False
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
if child.get('account_number') else account_name
account["parent_account"] = parent
account["expandable"] = True if identify_is_group(child) else False
account["value"] = (
(cstr(child.get("account_number")).strip() + " - " + account_name)
if child.get("account_number")
else account_name
)
accounts.append(account)
_import_accounts(child, account['value'])
_import_accounts(child, account["value"])
_import_accounts(chart, None)
return accounts

View File

@@ -21,6 +21,7 @@ charts = {}
all_account_types = []
all_roots = {}
def go():
global accounts, charts
default_account_types = get_default_account_types()
@@ -35,14 +36,16 @@ def go():
accounts, charts = {}, {}
country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = manifest.get("data", []) + manifest.get("init_xml", []) + \
manifest.get("update_xml", [])
data_files = (
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
)
files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path)
prefix = country_dir if csv_content else None
account_types = get_account_types(xml_roots.get("account.account.type", []),
csv_content.get("account.account.type", []), prefix)
account_types = get_account_types(
xml_roots.get("account.account.type", []), csv_content.get("account.account.type", []), prefix
)
account_types.update(default_account_types)
if xml_roots:
@@ -55,12 +58,15 @@ def go():
create_all_roots_file()
def get_default_account_types():
default_types_root = []
default_types_root.append(ET.parse(os.path.join(path, "account", "data",
"data_account_type.xml")).getroot())
default_types_root.append(
ET.parse(os.path.join(path, "account", "data", "data_account_type.xml")).getroot()
)
return get_account_types(default_types_root, None, prefix="account")
def get_xml_roots(files_path):
xml_roots = frappe._dict()
for filepath in files_path:
@@ -69,64 +75,69 @@ def get_xml_roots(files_path):
tree = ET.parse(filepath)
root = tree.getroot()
for node in root[0].findall("record"):
if node.get("model") in ["account.account.template",
"account.chart.template", "account.account.type"]:
if node.get("model") in [
"account.account.template",
"account.chart.template",
"account.account.type",
]:
xml_roots.setdefault(node.get("model"), []).append(root)
break
return xml_roots
def get_csv_contents(files_path):
csv_content = {}
for filepath in files_path:
fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type",
"account.chart.template"]:
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile:
try:
csv_content.setdefault(file_type, [])\
.append(read_csv_content(csvfile.read()))
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
except Exception as e:
continue
return csv_content
def get_account_types(root_list, csv_content, prefix=None):
types = {}
account_type_map = {
'cash': 'Cash',
'bank': 'Bank',
'tr_cash': 'Cash',
'tr_bank': 'Bank',
'receivable': 'Receivable',
'tr_receivable': 'Receivable',
'account rec': 'Receivable',
'payable': 'Payable',
'tr_payable': 'Payable',
'equity': 'Equity',
'stocks': 'Stock',
'stock': 'Stock',
'tax': 'Tax',
'tr_tax': 'Tax',
'tax-out': 'Tax',
'tax-in': 'Tax',
'charges_personnel': 'Chargeable',
'fixed asset': 'Fixed Asset',
'cogs': 'Cost of Goods Sold',
"cash": "Cash",
"bank": "Bank",
"tr_cash": "Cash",
"tr_bank": "Bank",
"receivable": "Receivable",
"tr_receivable": "Receivable",
"account rec": "Receivable",
"payable": "Payable",
"tr_payable": "Payable",
"equity": "Equity",
"stocks": "Stock",
"stock": "Stock",
"tax": "Tax",
"tr_tax": "Tax",
"tax-out": "Tax",
"tax-in": "Tax",
"charges_personnel": "Chargeable",
"fixed asset": "Fixed Asset",
"cogs": "Cost of Goods Sold",
}
for root in root_list:
for node in root[0].findall("record"):
if node.get("model")=="account.account.type":
if node.get("model") == "account.account.type":
data = {}
for field in node.findall("field"):
if field.get("name")=="code" and field.text.lower() != "none" \
and account_type_map.get(field.text):
data["account_type"] = account_type_map[field.text]
if (
field.get("name") == "code"
and field.text.lower() != "none"
and account_type_map.get(field.text)
):
data["account_type"] = account_type_map[field.text]
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
types[node_id] = data
if csv_content and csv_content[0][0]=="id":
if csv_content and csv_content[0][0] == "id":
for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row))
data = {}
@@ -137,21 +148,22 @@ def get_account_types(root_list, csv_content, prefix=None):
types[node_id] = data
return types
def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`"""
for model, root_list in iteritems(xml_roots):
for root in root_list:
for node in root[0].findall("record"):
if node.get("model")=="account.account.template":
if node.get("model") == "account.account.template":
data = {}
for field in node.findall("field"):
if field.get("name")=="name":
if field.get("name") == "name":
data["name"] = field.text
if field.get("name")=="parent_id":
if field.get("name") == "parent_id":
parent_id = field.get("ref") or field.get("eval")
data["parent_id"] = parent_id
if field.get("name")=="user_type":
if field.get("name") == "user_type":
value = field.get("ref")
if account_types.get(value, {}).get("account_type"):
data["account_type"] = account_types[value]["account_type"]
@@ -161,16 +173,17 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
data["children"] = []
accounts[node.get("id")] = data
if node.get("model")=="account.chart.template":
if node.get("model") == "account.chart.template":
data = {}
for field in node.findall("field"):
if field.get("name")=="name":
if field.get("name") == "name":
data["name"] = field.text
if field.get("name")=="account_root_id":
if field.get("name") == "account_root_id":
data["account_root_id"] = field.get("ref")
data["id"] = country_dir
charts.setdefault(node.get("id"), {}).update(data)
def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []):
for row in content[1:]:
@@ -178,7 +191,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
account = {
"name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
"children": []
"children": [],
}
user_type = data.get("user_type/id") or data.get("user_type:id")
if account_types.get(user_type, {}).get("account_type"):
@@ -195,12 +208,14 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
for row in content[1:]:
if row:
data = dict(zip(content[0], row))
charts.setdefault(data.get("id"), {}).update({
"account_root_id": data.get("account_root_id:id") or \
data.get("account_root_id/id"),
"name": data.get("name"),
"id": country_dir
})
charts.setdefault(data.get("id"), {}).update(
{
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
"name": data.get("name"),
"id": country_dir,
}
)
def make_account_trees():
"""build tree hierarchy"""
@@ -219,6 +234,7 @@ def make_account_trees():
if "children" in accounts[id] and not accounts[id].get("children"):
del accounts[id]["children"]
def make_charts():
"""write chart files in app/setup/doctype/company/charts"""
for chart_id in charts:
@@ -237,34 +253,38 @@ def make_charts():
chart["country_code"] = src["id"][5:]
chart["tree"] = accounts[src["account_root_id"]]
for key, val in chart["tree"].items():
if key in ["name", "parent_id"]:
chart["tree"].pop(key)
if type(val) == dict:
val["root_type"] = ""
if chart:
fpath = os.path.join("erpnext", "erpnext", "accounts", "doctype", "account",
"chart_of_accounts", filename + ".json")
fpath = os.path.join(
"erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
)
with open(fpath, "r") as chartfile:
old_content = chartfile.read()
if not old_content or (json.loads(old_content).get("is_active", "No") == "No" \
and json.loads(old_content).get("disabled", "No") == "No"):
if not old_content or (
json.loads(old_content).get("is_active", "No") == "No"
and json.loads(old_content).get("disabled", "No") == "No"
):
with open(fpath, "w") as chartfile:
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
all_roots.setdefault(filename, chart["tree"].keys())
def create_all_roots_file():
with open('all_roots.txt', 'w') as f:
with open("all_roots.txt", "w") as f:
for filename, roots in sorted(all_roots.items()):
f.write(filename)
f.write('\n----------------------\n')
f.write("\n----------------------\n")
for r in sorted(roots):
f.write(r.encode('utf-8'))
f.write('\n')
f.write('\n\n\n')
f.write(r.encode("utf-8"))
f.write("\n")
f.write("\n\n\n")
if __name__=="__main__":
if __name__ == "__main__":
go()

View File

@@ -7,182 +7,103 @@ from frappe import _
def get():
return {
_("Application of Funds (Assets)"): {
_("Current Assets"): {
_("Accounts Receivable"): {
_("Debtors"): {
"account_type": "Receivable"
}
},
_("Bank Accounts"): {
"account_type": "Bank",
"is_group": 1
},
_("Cash In Hand"): {
_("Cash"): {
"account_type": "Cash"
},
"account_type": "Cash"
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {
},
},
_("Securities and Deposits"): {
_("Earnest Money"): {}
},
_("Stock Assets"): {
_("Stock In Hand"): {
"account_type": "Stock"
},
"account_type": "Stock",
},
_("Tax Assets"): {
"is_group": 1
}
},
_("Fixed Assets"): {
_("Capital Equipments"): {
"account_type": "Fixed Asset"
},
_("Electronic Equipments"): {
"account_type": "Fixed Asset"
},
_("Furnitures and Fixtures"): {
"account_type": "Fixed Asset"
},
_("Office Equipments"): {
"account_type": "Fixed Asset"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset"
},
_("Buildings"): {
"account_type": "Fixed Asset"
_("Application of Funds (Assets)"): {
_("Current Assets"): {
_("Accounts Receivable"): {_("Debtors"): {"account_type": "Receivable"}},
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1},
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {},
},
_("Softwares"): {
"account_type": "Fixed Asset"
_("Securities and Deposits"): {_("Earnest Money"): {}},
_("Stock Assets"): {
_("Stock In Hand"): {"account_type": "Stock"},
"account_type": "Stock",
},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation"
},
_("CWIP Account"): {
"account_type": "Capital Work in Progress",
}
},
_("Investments"): {
"is_group": 1
},
_("Temporary Accounts"): {
_("Temporary Opening"): {
"account_type": "Temporary"
}
},
"root_type": "Asset"
},
_("Expenses"): {
_("Direct Expenses"): {
_("Stock Expenses"): {
_("Cost of Goods Sold"): {
"account_type": "Cost of Goods Sold"
},
_("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation"
},
_("Expenses Included In Valuation"): {
"account_type": "Expenses Included In Valuation"
},
_("Stock Adjustment"): {
"account_type": "Stock Adjustment"
}
},
},
_("Indirect Expenses"): {
_("Administrative Expenses"): {},
_("Commission on Sales"): {},
_("Depreciation"): {
"account_type": "Depreciation"
},
_("Entertainment Expenses"): {},
_("Freight and Forwarding Charges"): {
"account_type": "Chargeable"
},
_("Legal Expenses"): {},
_("Marketing Expenses"): {
"account_type": "Chargeable"
},
_("Miscellaneous Expenses"): {
"account_type": "Chargeable"
},
_("Office Maintenance Expenses"): {},
_("Office Rent"): {},
_("Postal Expenses"): {},
_("Print and Stationery"): {},
_("Round Off"): {
"account_type": "Round Off"
},
_("Salary"): {},
_("Sales Expenses"): {},
_("Telephone Expenses"): {},
_("Travel Expenses"): {},
_("Utility Expenses"): {},
_("Tax Assets"): {"is_group": 1},
},
_("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset"},
_("Electronic Equipments"): {"account_type": "Fixed Asset"},
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"},
_("Office Equipments"): {"account_type": "Fixed Asset"},
_("Plants and Machineries"): {"account_type": "Fixed Asset"},
_("Buildings"): {"account_type": "Fixed Asset"},
_("Softwares"): {"account_type": "Fixed Asset"},
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"},
_("CWIP Account"): {
"account_type": "Capital Work in Progress",
},
},
_("Investments"): {"is_group": 1},
_("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}},
"root_type": "Asset",
},
_("Expenses"): {
_("Direct Expenses"): {
_("Stock Expenses"): {
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold"},
_("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation"
},
_("Expenses Included In Valuation"): {"account_type": "Expenses Included In Valuation"},
_("Stock Adjustment"): {"account_type": "Stock Adjustment"},
},
},
_("Indirect Expenses"): {
_("Administrative Expenses"): {},
_("Commission on Sales"): {},
_("Depreciation"): {"account_type": "Depreciation"},
_("Entertainment Expenses"): {},
_("Freight and Forwarding Charges"): {"account_type": "Chargeable"},
_("Legal Expenses"): {},
_("Marketing Expenses"): {"account_type": "Chargeable"},
_("Miscellaneous Expenses"): {"account_type": "Chargeable"},
_("Office Maintenance Expenses"): {},
_("Office Rent"): {},
_("Postal Expenses"): {},
_("Print and Stationery"): {},
_("Round Off"): {"account_type": "Round Off"},
_("Salary"): {},
_("Sales Expenses"): {},
_("Telephone Expenses"): {},
_("Travel Expenses"): {},
_("Utility Expenses"): {},
_("Write Off"): {},
_("Exchange Gain/Loss"): {},
_("Gain/Loss on Asset Disposal"): {}
},
"root_type": "Expense"
},
_("Income"): {
_("Direct Income"): {
_("Sales"): {},
_("Service"): {}
},
_("Indirect Income"): {
"is_group": 1
},
"root_type": "Income"
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable"
},
_("Payroll Payable"): {},
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed"
}
},
_("Duties and Taxes"): {
"account_type": "Tax",
"is_group": 1
_("Gain/Loss on Asset Disposal"): {},
},
"root_type": "Expense",
},
_("Income"): {
_("Direct Income"): {_("Sales"): {}, _("Service"): {}},
_("Indirect Income"): {"is_group": 1},
"root_type": "Income",
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {"account_type": "Payable"},
_("Payroll Payable"): {},
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"},
_("Asset Received But Not Billed"): {"account_type": "Asset Received But Not Billed"},
},
_("Duties and Taxes"): {"account_type": "Tax", "is_group": 1},
_("Loans (Liabilities)"): {
_("Secured Loans"): {},
_("Unsecured Loans"): {},
_("Bank Overdraft Account"): {},
},
},
"root_type": "Liability"
},
},
"root_type": "Liability",
},
_("Equity"): {
_("Capital Stock"): {
"account_type": "Equity"
},
_("Dividends Paid"): {
"account_type": "Equity"
},
_("Opening Balance Equity"): {
"account_type": "Equity"
},
_("Retained Earnings"): {
"account_type": "Equity"
},
"root_type": "Equity"
}
_("Capital Stock"): {"account_type": "Equity"},
_("Dividends Paid"): {"account_type": "Equity"},
_("Opening Balance Equity"): {"account_type": "Equity"},
_("Retained Earnings"): {"account_type": "Equity"},
"root_type": "Equity",
},
}

View File

@@ -6,288 +6,153 @@ from frappe import _
def get():
return {
_("Application of Funds (Assets)"): {
_("Current Assets"): {
_("Accounts Receivable"): {
_("Debtors"): {
"account_type": "Receivable",
"account_number": "1310"
},
"account_number": "1300"
},
_("Bank Accounts"): {
"account_type": "Bank",
"is_group": 1,
"account_number": "1200"
},
_("Cash In Hand"): {
_("Cash"): {
"account_type": "Cash",
"account_number": "1110"
},
"account_type": "Cash",
"account_number": "1100"
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {
"account_number": "1610"
},
"account_number": "1600"
},
_("Securities and Deposits"): {
_("Earnest Money"): {
"account_number": "1651"
},
"account_number": "1650"
},
_("Stock Assets"): {
_("Stock In Hand"): {
"account_type": "Stock",
"account_number": "1410"
},
"account_type": "Stock",
"account_number": "1400"
},
_("Tax Assets"): {
"is_group": 1,
"account_number": "1500"
},
"account_number": "1100-1600"
},
_("Fixed Assets"): {
_("Capital Equipments"): {
"account_type": "Fixed Asset",
"account_number": "1710"
},
_("Electronic Equipments"): {
"account_type": "Fixed Asset",
"account_number": "1720"
},
_("Furnitures and Fixtures"): {
"account_type": "Fixed Asset",
"account_number": "1730"
},
_("Office Equipments"): {
"account_type": "Fixed Asset",
"account_number": "1740"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset",
"account_number": "1750"
},
_("Buildings"): {
"account_type": "Fixed Asset",
"account_number": "1760"
},
_("Softwares"): {
"account_type": "Fixed Asset",
"account_number": "1770"
},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation",
"account_number": "1780"
},
_("CWIP Account"): {
"account_type": "Capital Work in Progress",
"account_number": "1790"
},
"account_number": "1700"
},
_("Investments"): {
"is_group": 1,
"account_number": "1800"
},
_("Temporary Accounts"): {
_("Temporary Opening"): {
"account_type": "Temporary",
"account_number": "1910"
},
"account_number": "1900"
},
"root_type": "Asset",
"account_number": "1000"
},
_("Expenses"): {
_("Direct Expenses"): {
_("Stock Expenses"): {
_("Cost of Goods Sold"): {
"account_type": "Cost of Goods Sold",
"account_number": "5111"
},
_("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation",
"account_number": "5112"
},
_("Expenses Included In Valuation"): {
"account_type": "Expenses Included In Valuation",
"account_number": "5118"
},
_("Stock Adjustment"): {
"account_type": "Stock Adjustment",
"account_number": "5119"
},
"account_number": "5110"
},
"account_number": "5100"
},
_("Indirect Expenses"): {
_("Administrative Expenses"): {
"account_number": "5201"
},
_("Commission on Sales"): {
"account_number": "5202"
},
_("Depreciation"): {
"account_type": "Depreciation",
"account_number": "5203"
},
_("Entertainment Expenses"): {
"account_number": "5204"
},
_("Freight and Forwarding Charges"): {
"account_type": "Chargeable",
"account_number": "5205"
},
_("Legal Expenses"): {
"account_number": "5206"
},
_("Marketing Expenses"): {
"account_type": "Chargeable",
"account_number": "5207"
},
_("Office Maintenance Expenses"): {
"account_number": "5208"
},
_("Office Rent"): {
"account_number": "5209"
},
_("Postal Expenses"): {
"account_number": "5210"
},
_("Print and Stationery"): {
"account_number": "5211"
},
_("Round Off"): {
"account_type": "Round Off",
"account_number": "5212"
},
_("Salary"): {
"account_number": "5213"
},
_("Sales Expenses"): {
"account_number": "5214"
},
_("Telephone Expenses"): {
"account_number": "5215"
},
_("Travel Expenses"): {
"account_number": "5216"
},
_("Utility Expenses"): {
"account_number": "5217"
},
_("Write Off"): {
"account_number": "5218"
},
_("Exchange Gain/Loss"): {
"account_number": "5219"
},
_("Gain/Loss on Asset Disposal"): {
"account_number": "5220"
},
_("Miscellaneous Expenses"): {
"account_type": "Chargeable",
"account_number": "5221"
},
"account_number": "5200"
},
"root_type": "Expense",
"account_number": "5000"
},
_("Income"): {
_("Direct Income"): {
_("Sales"): {
"account_number": "4110"
},
_("Service"): {
"account_number": "4120"
},
"account_number": "4100"
},
_("Indirect Income"): {
"is_group": 1,
"account_number": "4200"
},
"root_type": "Income",
"account_number": "4000"
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable",
"account_number": "2110"
},
_("Payroll Payable"): {
"account_number": "2120"
},
"account_number": "2100"
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed",
"account_number": "2210"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed",
"account_number": "2211"
},
"account_number": "2200"
},
_("Duties and Taxes"): {
_("TDS Payable"): {
"account_number": "2310"
},
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"
},
_("Loans (Liabilities)"): {
_("Secured Loans"): {
"account_number": "2410"
},
_("Unsecured Loans"): {
"account_number": "2420"
},
_("Bank Overdraft Account"): {
"account_number": "2430"
},
"account_number": "2400"
},
"account_number": "2100-2400"
},
"root_type": "Liability",
"account_number": "2000"
},
_("Equity"): {
_("Capital Stock"): {
"account_type": "Equity",
"account_number": "3100"
},
_("Dividends Paid"): {
"account_type": "Equity",
"account_number": "3200"
},
_("Opening Balance Equity"): {
"account_type": "Equity",
"account_number": "3300"
},
_("Retained Earnings"): {
"account_type": "Equity",
"account_number": "3400"
},
"root_type": "Equity",
"account_number": "3000"
}
}
return {
_("Application of Funds (Assets)"): {
_("Current Assets"): {
_("Accounts Receivable"): {
_("Debtors"): {"account_type": "Receivable", "account_number": "1310"},
"account_number": "1300",
},
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"},
_("Cash In Hand"): {
_("Cash"): {"account_type": "Cash", "account_number": "1110"},
"account_type": "Cash",
"account_number": "1100",
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {"account_number": "1610"},
"account_number": "1600",
},
_("Securities and Deposits"): {
_("Earnest Money"): {"account_number": "1651"},
"account_number": "1650",
},
_("Stock Assets"): {
_("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"},
"account_type": "Stock",
"account_number": "1400",
},
_("Tax Assets"): {"is_group": 1, "account_number": "1500"},
"account_number": "1100-1600",
},
_("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"},
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"},
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"},
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"},
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"},
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation",
"account_number": "1780",
},
_("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"},
"account_number": "1700",
},
_("Investments"): {"is_group": 1, "account_number": "1800"},
_("Temporary Accounts"): {
_("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"},
"account_number": "1900",
},
"root_type": "Asset",
"account_number": "1000",
},
_("Expenses"): {
_("Direct Expenses"): {
_("Stock Expenses"): {
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"},
_("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation",
"account_number": "5112",
},
_("Expenses Included In Valuation"): {
"account_type": "Expenses Included In Valuation",
"account_number": "5118",
},
_("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"},
"account_number": "5110",
},
"account_number": "5100",
},
_("Indirect Expenses"): {
_("Administrative Expenses"): {"account_number": "5201"},
_("Commission on Sales"): {"account_number": "5202"},
_("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"},
_("Entertainment Expenses"): {"account_number": "5204"},
_("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"},
_("Legal Expenses"): {"account_number": "5206"},
_("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"},
_("Office Maintenance Expenses"): {"account_number": "5208"},
_("Office Rent"): {"account_number": "5209"},
_("Postal Expenses"): {"account_number": "5210"},
_("Print and Stationery"): {"account_number": "5211"},
_("Round Off"): {"account_type": "Round Off", "account_number": "5212"},
_("Salary"): {"account_number": "5213"},
_("Sales Expenses"): {"account_number": "5214"},
_("Telephone Expenses"): {"account_number": "5215"},
_("Travel Expenses"): {"account_number": "5216"},
_("Utility Expenses"): {"account_number": "5217"},
_("Write Off"): {"account_number": "5218"},
_("Exchange Gain/Loss"): {"account_number": "5219"},
_("Gain/Loss on Asset Disposal"): {"account_number": "5220"},
_("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"},
"account_number": "5200",
},
"root_type": "Expense",
"account_number": "5000",
},
_("Income"): {
_("Direct Income"): {
_("Sales"): {"account_number": "4110"},
_("Service"): {"account_number": "4120"},
"account_number": "4100",
},
_("Indirect Income"): {"is_group": 1, "account_number": "4200"},
"root_type": "Income",
"account_number": "4000",
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {"account_type": "Payable", "account_number": "2110"},
_("Payroll Payable"): {"account_number": "2120"},
"account_number": "2100",
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed",
"account_number": "2210",
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed",
"account_number": "2211",
},
"account_number": "2200",
},
_("Duties and Taxes"): {
_("TDS Payable"): {"account_number": "2310"},
"account_type": "Tax",
"is_group": 1,
"account_number": "2300",
},
_("Loans (Liabilities)"): {
_("Secured Loans"): {"account_number": "2410"},
_("Unsecured Loans"): {"account_number": "2420"},
_("Bank Overdraft Account"): {"account_number": "2430"},
"account_number": "2400",
},
"account_number": "2100-2400",
},
"root_type": "Liability",
"account_number": "2000",
},
_("Equity"): {
_("Capital Stock"): {"account_type": "Equity", "account_number": "3100"},
_("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"},
_("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"},
_("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"},
"root_type": "Equity",
"account_number": "3000",
},
}

View File

@@ -20,8 +20,9 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company"
acc.insert()
account_number, account_name = frappe.db.get_value("Account", "1210 - Debtors - _TC",
["account_number", "account_name"])
account_number, account_name = frappe.db.get_value(
"Account", "1210 - Debtors - _TC", ["account_number", "account_name"]
)
self.assertEqual(account_number, "1210")
self.assertEqual(account_name, "Debtors")
@@ -30,8 +31,12 @@ class TestAccount(unittest.TestCase):
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
["account_name", "account_number"], as_dict=1)
new_acc = frappe.db.get_value(
"Account",
"1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
["account_name", "account_number"],
as_dict=1,
)
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
@@ -79,7 +84,9 @@ class TestAccount(unittest.TestCase):
self.assertEqual(parent, "Securities and Deposits - _TC")
merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
merge_account(
"Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
)
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging
@@ -91,14 +98,28 @@ class TestAccount(unittest.TestCase):
doc = frappe.get_doc("Account", "Current Assets - _TC")
# Raise error as is_group property doesn't match
self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\
"Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
self.assertRaises(
frappe.ValidationError,
merge_account,
"Current Assets - _TC",
"Accumulated Depreciation - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
doc = frappe.get_doc("Account", "Capital Stock - _TC")
# Raise error as root_type property doesn't match
self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
self.assertRaises(
frappe.ValidationError,
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
def test_account_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
@@ -109,8 +130,12 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3"
acc.insert()
acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"})
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"})
acc_tc_4 = frappe.db.get_value(
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 4"}
)
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 5"}
)
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
@@ -138,8 +163,26 @@ class TestAccount(unittest.TestCase):
update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
self.assertTrue(
frappe.db.exists(
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 4",
"account_number": "1234",
},
)
)
self.assertTrue(
frappe.db.exists(
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 5",
"account_number": "1234",
},
)
)
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
@@ -155,25 +198,71 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3"
acc.insert()
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
)
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
)
# Try renaming child company account
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
# Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
self.assertTrue(
frappe.db.exists(
"Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
)
)
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
to_delete = [
"Test Group Account - _TC3",
"Test Group Account - _TC4",
"Test Modified Account - _TC5",
]
for doc in to_delete:
frappe.delete_doc("Account", doc)
def test_validate_account_currency(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
if not frappe.db.get_value("Account", "Test Currency Account - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Currency Account"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
else:
acc = frappe.get_doc("Account", "Test Currency Account - _TC")
self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account
make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)
def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects
@@ -184,20 +273,16 @@ def _make_test_records(verbose=None):
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
["_Test Cash", "Cash In Hand", 0, "Cash", None],
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
["_Test Employee Advance", "Current Liabilities", 0, None, None],
["_Test Account Tax Assets", "Current Assets", 1, None, None],
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
@@ -206,38 +291,45 @@ def _make_test_records(verbose=None):
["_Test Account Discount", "Direct Expenses", 0, None, None],
["_Test Write Off", "Indirect Expenses", 0, None, None],
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
["_Test Account Sales", "Direct Income", 0, None, None],
# related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None],
["_Test Payable", "Current Liabilities", 0, "Payable", None],
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"],
]
for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
test_objects = make_test_objects("Account", [{
"doctype": "Account",
"account_name": account_name,
"parent_account": parent_account + " - " + abbr,
"company": company,
"is_group": is_group,
"account_type": account_type,
"account_currency": currency
} for account_name, parent_account, is_group, account_type, currency in accounts])
for company, abbr in [
["_Test Company", "_TC"],
["_Test Company 1", "_TC1"],
["_Test Company with perpetual inventory", "TCP1"],
]:
test_objects = make_test_objects(
"Account",
[
{
"doctype": "Account",
"account_name": account_name,
"parent_account": parent_account + " - " + abbr,
"company": company,
"is_group": is_group,
"account_type": account_type,
"account_currency": currency,
}
for account_name, parent_account, is_group, account_type, currency in accounts
],
)
return test_objects
def get_inventory_account(company, warehouse=None):
account = None
if warehouse:
@@ -247,19 +339,24 @@ def get_inventory_account(company, warehouse=None):
return account
def create_account(**kwargs):
account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
account = frappe.db.get_value(
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
)
if account:
return account
else:
account = frappe.get_doc(dict(
doctype = "Account",
account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'),
company = kwargs.get('company'),
account_currency = kwargs.get('account_currency')
))
account = frappe.get_doc(
dict(
doctype="Account",
account_name=kwargs.get("account_name"),
account_type=kwargs.get("account_type"),
parent_account=kwargs.get("parent_account"),
company=kwargs.get("company"),
account_currency=kwargs.get("account_currency"),
)
)
account.save()
return account.name

View File

@@ -17,13 +17,21 @@ class AccountingDimension(Document):
self.set_fieldname_and_label()
def validate(self):
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
if self.document_type in core_doctypes_list + (
"Accounting Dimension",
"Project",
"Cost Center",
"Accounting Dimension Detail",
"Company",
"Account",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
exists = frappe.db.get_value(
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension"))
@@ -42,13 +50,13 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue='long')
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
def set_fieldname_and_label(self):
if not self.label:
@@ -60,6 +68,7 @@ class AccountingDimension(Document):
def on_update(self):
frappe.flags.accounting_dimensions = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
@@ -70,9 +79,9 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = 'dimension_col_break'
insert_after_field = "dimension_col_break"
else:
insert_after_field = 'accounting_dimensions_section'
insert_after_field = "accounting_dimensions_section"
df = {
"fieldname": doc.fieldname,
@@ -80,13 +89,13 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"fieldtype": "Link",
"options": doc.document_type,
"insert_after": insert_after_field,
"owner": "Administrator"
"owner": "Administrator",
}
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
if df['fieldname'] not in fieldnames:
if df["fieldname"] not in fieldnames:
if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
@@ -94,14 +103,17 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
count += 1
frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions..."))
frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions..."))
frappe.clear_cache(doctype=doctype)
def add_dimension_to_budget_doctype(df, doc):
df.update({
"insert_after": "cost_center",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type)
})
df.update(
{
"insert_after": "cost_center",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
}
)
create_custom_field("Budget", df)
@@ -112,36 +124,44 @@ def add_dimension_to_budget_doctype(df, doc):
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
property_setter_doc.save()
frappe.clear_cache(doctype='Budget')
frappe.clear_cache(doctype="Budget")
else:
frappe.get_doc({
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"doc_type": "Budget",
"field_name": "budget_against",
"property": "options",
"property_type": "Text",
"value": "\nCost Center\nProject\n" + doc.document_type
}).insert(ignore_permissions=True)
frappe.get_doc(
{
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"doc_type": "Budget",
"field_name": "budget_against",
"property": "options",
"property_type": "Text",
"value": "\nCost Center\nProject\n" + doc.document_type,
}
).insert(ignore_permissions=True)
def delete_accounting_dimension(doc):
doclist = get_doctypes_with_dimensions()
frappe.db.sql("""
frappe.db.sql(
"""
DELETE FROM `tabCustom Field`
WHERE fieldname = %s
AND dt IN (%s)""" % #nosec
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
AND dt IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
)
frappe.db.sql("""
frappe.db.sql(
"""
DELETE FROM `tabProperty Setter`
WHERE field_name = %s
AND doc_type IN (%s)""" % #nosec
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
AND doc_type IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
)
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
value_list = budget_against_property.value.split('\n')[3:]
value_list = budget_against_property.value.split("\n")[3:]
if doc.document_type in value_list:
value_list.remove(doc.document_type)
@@ -152,6 +172,7 @@ def delete_accounting_dimension(doc):
for doctype in doclist:
frappe.clear_cache(doctype=doctype)
@frappe.whitelist()
def disable_dimension(doc):
if frappe.flags.in_test:
@@ -159,10 +180,11 @@ def disable_dimension(doc):
else:
frappe.enqueue(toggle_disabling, doc=doc)
def toggle_disabling(doc):
doc = json.loads(doc)
if doc.get('disabled'):
if doc.get("disabled"):
df = {"read_only": 1}
else:
df = {"read_only": 0}
@@ -170,7 +192,7 @@ def toggle_disabling(doc):
doclist = get_doctypes_with_dimensions()
for doctype in doclist:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')})
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get("fieldname")})
if field:
custom_field = frappe.get_doc("Custom Field", field)
custom_field.update(df)
@@ -178,26 +200,34 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes")
def get_accounting_dimensions(as_list=True):
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"])
frappe.flags.accounting_dimensions = frappe.get_all(
"Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]
)
if as_list:
return [d.fieldname for d in frappe.flags.accounting_dimensions]
else:
return frappe.flags.accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
dimensions = frappe.db.sql(
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""", as_dict=1)
WHERE p.name = c.parent""",
as_dict=1,
)
return dimensions
def get_dimension_with_children(doctype, dimension):
if isinstance(dimension, list):
@@ -205,34 +235,39 @@ def get_dimension_with_children(doctype, dimension):
all_dimensions = []
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children]
return all_dimensions
@frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False):
dimension_filters = frappe.db.sql("""
dimension_filters = frappe.db.sql(
"""
SELECT label, fieldname, document_type
FROM `tabAccounting Dimension`
WHERE disabled = 0
""", as_dict=1)
""",
as_dict=1,
)
default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
default_dimensions = frappe.db.sql(
"""SELECT p.fieldname, c.company, c.default_dimension
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""", as_dict=1)
WHERE c.parent = p.name""",
as_dict=1,
)
if with_cost_center_and_project:
dimension_filters.extend([
{
'fieldname': 'cost_center',
'document_type': 'Cost Center'
},
{
'fieldname': 'project',
'document_type': 'Project'
}
])
dimension_filters.extend(
[
{"fieldname": "cost_center", "document_type": "Cost Center"},
{"fieldname": "project", "document_type": "Project"},
]
)
default_dimensions_map = {}
for dimension in default_dimensions:

View File

@@ -8,7 +8,8 @@ import frappe
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"]
class TestAccountingDimension(unittest.TestCase):
def setUp(self):
@@ -18,24 +19,27 @@ class TestAccountingDimension(unittest.TestCase):
si = create_sales_invoice(do_not_save=1)
si.location = "Block 1"
si.append("items", {
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 100,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"department": "_Test Department - _TC",
"location": "Block 1"
})
si.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 100,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"department": "_Test Department - _TC",
"location": "Block 1",
},
)
si.save()
si.submit()
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
self.assertEqual(gle.get('department'), "_Test Department - _TC")
self.assertEqual(gle.get("department"), "_Test Department - _TC")
def test_dimension_against_journal_entry(self):
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
@@ -50,21 +54,24 @@ class TestAccountingDimension(unittest.TestCase):
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
self.assertEqual(gle.get('department'), "_Test Department - _TC")
self.assertEqual(gle1.get('department'), "_Test Department - _TC")
self.assertEqual(gle.get("department"), "_Test Department - _TC")
self.assertEqual(gle1.get("department"), "_Test Department - _TC")
def test_mandatory(self):
si = create_sales_invoice(do_not_save=1)
si.append("items", {
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 100,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"location": ""
})
si.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 100,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"location": "",
},
)
si.save()
self.assertRaises(frappe.ValidationError, si.submit)
@@ -72,31 +79,39 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self):
disable_dimension()
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
}
).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1 = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Location",
}
)
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1,
},
)
dimension1.insert()
dimension1.save()
@@ -105,6 +120,7 @@ def create_dimension():
dimension1.disabled = 0
dimension1.save()
def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 1

View File

@@ -19,17 +19,27 @@ class AccountingDimensionFilter(Document):
WHERE d.name = a.parent
and d.name != %s
and d.accounting_dimension = %s
""", (self.name, self.accounting_dimension), as_dict=1)
""",
(self.name, self.accounting_dimension),
as_dict=1,
)
account_list = [d.account for d in accounts]
for account in self.get('accounts'):
for account in self.get("accounts"):
if account.applicable_on_account in account_list:
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
frappe.throw(
_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
account.idx,
frappe.bold(account.applicable_on_account),
frappe.bold(self.accounting_dimension),
)
)
def get_dimension_filter_map():
filters = frappe.db.sql("""
filters = frappe.db.sql(
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
@@ -40,22 +50,30 @@ def get_dimension_filter_map():
p.name = a.parent
AND p.disabled = 0
AND p.name = d.parent
""", as_dict=1)
""",
as_dict=1,
)
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
f.allow_or_restrict, f.is_mandatory)
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
return dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
map_object.setdefault((dimension, account), {
'allowed_dimensions': [],
'is_mandatory': is_mandatory,
'allow_or_restrict': allow_or_restrict
})
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
map_object.setdefault(
(dimension, account),
{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
)
map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)

View File

@@ -12,7 +12,8 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
test_dependencies = ['Location', 'Cost Center', 'Department']
test_dependencies = ["Location", "Cost Center", "Department"]
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
@@ -22,9 +23,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def test_allowed_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.items[0].cost_center = 'Main - _TC'
si.department = 'Accounts - _TC'
si.location = 'Block 1'
si.items[0].cost_center = "Main - _TC"
si.department = "Accounts - _TC"
si.location = "Block 1"
si.save()
self.assertRaises(InvalidAccountDimensionError, si.submit)
@@ -32,12 +33,12 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def test_mandatory_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.department = ''
si.location = 'Block 1'
si.department = ""
si.location = "Block 1"
# Test with no department for Sales Account
si.items[0].department = ''
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
si.items[0].department = ""
si.items[0].cost_center = "_Test Cost Center 2 - _TC"
si.save()
self.assertRaises(MandatoryAccountDimensionError, si.submit)
@@ -52,53 +53,54 @@ class TestAccountingDimensionFilter(unittest.TestCase):
if si.docstatus == 1:
si.cancel()
def create_accounting_dimension_filter():
if not frappe.db.get_value('Accounting Dimension Filter',
{'accounting_dimension': 'Cost Center'}):
frappe.get_doc({
'doctype': 'Accounting Dimension Filter',
'accounting_dimension': 'Cost Center',
'allow_or_restrict': 'Allow',
'company': '_Test Company',
'accounts': [{
'applicable_on_account': 'Sales - _TC',
}],
'dimensions': [{
'accounting_dimension': 'Cost Center',
'dimension_value': '_Test Cost Center 2 - _TC'
}]
}).insert()
if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Cost Center",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [
{
"applicable_on_account": "Sales - _TC",
}
],
"dimensions": [
{"accounting_dimension": "Cost Center", "dimension_value": "_Test Cost Center 2 - _TC"}
],
}
).insert()
else:
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 0
doc.save()
if not frappe.db.get_value('Accounting Dimension Filter',
{'accounting_dimension': 'Department'}):
frappe.get_doc({
'doctype': 'Accounting Dimension Filter',
'accounting_dimension': 'Department',
'allow_or_restrict': 'Allow',
'company': '_Test Company',
'accounts': [{
'applicable_on_account': 'Sales - _TC',
'is_mandatory': 1
}],
'dimensions': [{
'accounting_dimension': 'Department',
'dimension_value': 'Accounts - _TC'
}]
}).insert()
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Department"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Department",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
"dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],
}
).insert()
else:
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 0
doc.save()
def disable_dimension_filter():
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 1
doc.save()
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 1
doc.save()

View File

@@ -7,7 +7,9 @@ from frappe import _
from frappe.model.document import Document
class OverlapError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError):
pass
class AccountingPeriod(Document):
def validate(self):
@@ -17,11 +19,12 @@ class AccountingPeriod(Document):
self.bootstrap_doctypes_for_closing()
def autoname(self):
company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
company_abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = " - ".join([self.period_name, company_abbr])
def validate_overlap(self):
existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period`
existing_accounting_period = frappe.db.sql(
"""select name from `tabAccounting Period`
where (
(%(start_date)s between start_date and end_date)
or (%(end_date)s between start_date and end_date)
@@ -32,18 +35,29 @@ class AccountingPeriod(Document):
"start_date": self.start_date,
"end_date": self.end_date,
"name": self.name,
"company": self.company
}, as_dict=True)
"company": self.company,
},
as_dict=True,
)
if len(existing_accounting_period) > 0:
frappe.throw(_("Accounting Period overlaps with {0}")
.format(existing_accounting_period[0].get("name")), OverlapError)
frappe.throw(
_("Accounting Period overlaps with {0}").format(existing_accounting_period[0].get("name")),
OverlapError,
)
@frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
"Bank Clearance", "Asset", "Stock Entry"]
doctypes = [
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)
@@ -53,7 +67,7 @@ class AccountingPeriod(Document):
def bootstrap_doctypes_for_closing(self):
if len(self.closed_documents) == 0:
for doctype_for_closing in self.get_doctypes_for_closing():
self.append('closed_documents', {
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed
})
self.append(
"closed_documents",
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
)

View File

@@ -10,29 +10,38 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
test_dependencies = ['Item']
test_dependencies = ["Item"]
class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self):
ap1 = create_accounting_period(start_date = "2018-04-01",
end_date = "2018-06-30", company = "Wind Power LLC")
ap1 = create_accounting_period(
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
)
ap1.save()
ap2 = create_accounting_period(start_date = "2018-06-30",
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
ap2 = create_accounting_period(
start_date="2018-06-30",
end_date="2018-07-10",
company="Wind Power LLC",
period_name="Test Accounting Period 1",
)
self.assertRaises(OverlapError, ap2.save)
def test_accounting_period(self):
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save()
doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
doc = create_sales_invoice(
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name)
def create_accounting_period(**args):
args = frappe._dict(args)
@@ -41,8 +50,6 @@ def create_accounting_period(**args):
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company"
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1})
return accounting_period

View File

@@ -18,11 +18,13 @@ class AccountsSettings(Document):
frappe.clear_cache()
def validate(self):
frappe.db.set_default("add_taxes_from_item_tax_template",
self.get("add_taxes_from_item_tax_template", 0))
frappe.db.set_default(
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
)
frappe.db.set_default("enable_common_party_accounting",
self.get("enable_common_party_accounting", 0))
frappe.db.set_default(
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
)
self.validate_stale_days()
self.enable_payment_schedule_in_print()
@@ -32,34 +34,91 @@ class AccountsSettings(Document):
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
_("Stale Days should start from 1."), title='Error', indicator='red',
raise_exception=1)
_("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1
)
def enable_payment_schedule_in_print(self):
show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
make_property_setter(
doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False
)
make_property_setter(
doctype,
"payment_schedule",
"print_hide",
0 if show_in_print else 1,
"Check",
validate_fields_for_doctype=False,
)
def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"additional_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
make_property_setter(
"Item",
"default_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
def validate_pending_reposts(self):
if self.acc_frozen_upto:

View File

@@ -1,4 +1,3 @@
import unittest
import frappe
@@ -8,12 +7,12 @@ class TestAccountsSettings(unittest.TestCase):
def tearDown(self):
# Just in case `save` method succeeds, we need to take things back to default so that other tests
# don't break
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
cur_settings.allow_stale = 1
cur_settings.save()
def test_stale_days(self):
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
cur_settings.allow_stale = 0
cur_settings.stale_days = 0

View File

@@ -15,4 +15,4 @@ class Bank(Document):
load_address_and_contact(self)
def on_trash(self):
delete_contact_and_address('Bank', self.name)
delete_contact_and_address("Bank", self.name)

View File

@@ -1,14 +1,8 @@
from frappe import _
def get_data():
return {
'fieldname': 'bank',
'transactions': [
{
'label': _('Bank Details'),
'items': ['Bank Account', 'Bank Guarantee']
}
]
"fieldname": "bank",
"transactions": [{"label": _("Bank Details"), "items": ["Bank Account", "Bank Guarantee"]}],
}

View File

@@ -20,7 +20,7 @@ class BankAccount(Document):
self.name = self.account_name + " - " + self.bank
def on_trash(self):
delete_contact_and_address('BankAccount', self.name)
delete_contact_and_address("BankAccount", self.name)
def validate(self):
self.validate_company()
@@ -31,9 +31,9 @@ class BankAccount(Document):
frappe.throw(_("Company is manadatory for company account"))
def validate_iban(self):
'''
"""
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
'''
"""
# IBAN field is optional
if not self.iban:
return
@@ -43,7 +43,7 @@ class BankAccount(Document):
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = ''.join(self.iban.split(' ')).upper()
iban = "".join(self.iban.split(" ")).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
@@ -52,12 +52,12 @@ class BankAccount(Document):
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
to_check = int(''.join(encoded))
to_check = int("".join(encoded))
except ValueError:
frappe.throw(_('IBAN is not valid'))
frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
frappe.throw(_('IBAN is not valid'))
frappe.throw(_("IBAN is not valid"))
@frappe.whitelist()
@@ -69,12 +69,14 @@ def make_bank_account(doctype, docname):
return doc
@frappe.whitelist()
def get_party_bank_account(party_type, party):
return frappe.db.get_value(party_type,
party, 'default_bank_account')
return frappe.db.get_value(party_type, party, "default_bank_account")
@frappe.whitelist()
def get_bank_account_details(bank_account):
return frappe.db.get_value("Bank Account",
bank_account, ['account', 'bank', 'bank_account_no'], as_dict=1)
return frappe.db.get_value(
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
)

View File

@@ -1,28 +1,20 @@
from frappe import _
def get_data():
return {
'fieldname': 'bank_account',
'non_standard_fieldnames': {
'Customer': 'default_bank_account',
'Supplier': 'default_bank_account',
"fieldname": "bank_account",
"non_standard_fieldnames": {
"Customer": "default_bank_account",
"Supplier": "default_bank_account",
},
'transactions': [
"transactions": [
{
'label': _('Payments'),
'items': ['Payment Entry', 'Payment Request', 'Payment Order', 'Payroll Entry']
"label": _("Payments"),
"items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
},
{
'label': _('Party'),
'items': ['Customer', 'Supplier']
},
{
'items': ['Bank Guarantee']
},
{
'items': ['Journal Entry']
}
]
{"label": _("Party"), "items": ["Customer", "Supplier"]},
{"items": ["Bank Guarantee"]},
{"items": ["Journal Entry"]},
],
}

View File

@@ -8,28 +8,28 @@ from frappe import ValidationError
# test_records = frappe.get_test_records('Bank Account')
class TestBankAccount(unittest.TestCase):
class TestBankAccount(unittest.TestCase):
def test_validate_iban(self):
valid_ibans = [
'GB82 WEST 1234 5698 7654 32',
'DE91 1000 0000 0123 4567 89',
'FR76 3000 6000 0112 3456 7890 189'
"GB82 WEST 1234 5698 7654 32",
"DE91 1000 0000 0123 4567 89",
"FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
'GB72 WEST 1234 5698 7654 32',
'DE81 1000 0000 0123 4567 89',
'FR66 3000 6000 0112 3456 7890 189'
"GB72 WEST 1234 5698 7654 32",
"DE81 1000 0000 0123 4567 89",
"FR66 3000 6000 0112 3456 7890 189",
]
bank_account = frappe.get_doc({'doctype':'Bank Account'})
bank_account = frappe.get_doc({"doctype": "Bank Account"})
try:
bank_account.validate_iban()
except AttributeError:
msg = 'BankAccount.validate_iban() failed for empty IBAN'
msg = "BankAccount.validate_iban() failed for empty IBAN"
self.fail(msg=msg)
for iban in valid_ibans:
@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try:
bank_account.validate_iban()
except ValidationError:
msg = 'BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = 'BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()

View File

@@ -7,9 +7,8 @@ from frappe import _, msgprint
from frappe.model.document import Document
from frappe.utils import flt, fmt_money, getdate, nowdate
form_grid_templates = {
"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"
}
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
class BankClearance(Document):
@frappe.whitelist()
@@ -24,7 +23,8 @@ class BankClearance(Document):
if not self.include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql("""
journal_entries = frappe.db.sql(
"""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
@@ -38,12 +38,18 @@ class BankClearance(Document):
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
""".format(
condition=condition
),
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
if self.bank_account:
condition += 'and bank_account = %(bank_account)s'
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql("""
payment_entries = frappe.db.sql(
"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
@@ -58,12 +64,22 @@ class BankClearance(Document):
{condition}
order by
posting_date ASC, name DESC
""".format(condition=condition), {"account": self.account, "from":self.from_date,
"to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
""".format(
condition=condition
),
{
"account": self.account,
"from": self.from_date,
"to": self.to_date,
"bank_account": self.bank_account,
},
as_dict=1,
)
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
pos_sales_invoices = frappe.db.sql("""
pos_sales_invoices = frappe.db.sql(
"""
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.customer as against_account, sip.clearance_date,
@@ -74,9 +90,13 @@ class BankClearance(Document):
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
order by
si.posting_date ASC, si.name DESC
""", {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
""",
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
pos_purchase_invoices = frappe.db.sql("""
pos_purchase_invoices = frappe.db.sql(
"""
select
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
@@ -87,18 +107,24 @@ class BankClearance(Document):
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
order by
pi.posting_date ASC, pi.name DESC
""", {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
""",
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
key=lambda k: k['posting_date'] or getdate(nowdate()))
entries = sorted(
list(payment_entries)
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
key=lambda k: k["posting_date"] or getdate(nowdate()),
)
self.set('payment_entries', [])
self.set("payment_entries", [])
self.total_amount = 0.0
for d in entries:
row = self.append('payment_entries', {})
row = self.append("payment_entries", {})
amount = flt(d.get('debit', 0)) - flt(d.get('credit', 0))
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
@@ -112,21 +138,24 @@ class BankClearance(Document):
@frappe.whitelist()
def update_clearance_date(self):
clearance_date_updated = False
for d in self.get('payment_entries'):
for d in self.get("payment_entries"):
if d.clearance_date:
if not d.payment_document:
frappe.throw(_("Row #{0}: Payment document is required to complete the transaction"))
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
frappe.throw(_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}")
.format(d.idx, d.clearance_date, d.cheque_date))
frappe.throw(
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
d.idx, d.clearance_date, d.cheque_date
)
)
if d.clearance_date or self.include_reconciled_entries:
if not d.clearance_date:
d.clearance_date = None
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
payment_entry.db_set('clearance_date', d.clearance_date)
payment_entry.db_set("clearance_date", d.clearance_date)
clearance_date_updated = True

View File

@@ -23,10 +23,16 @@ class BankGuarantee(Document):
if not self.bank:
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
@frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
return frappe.db.sql(
""" select {columns} from `tab{doctype}` where name=%s""".format(
columns=", ".join(column_list), doctype=doctype
),
docname,
as_dict=1,
)[0]

View File

@@ -14,6 +14,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
});
},
onload: function (frm) {
frm.trigger('bank_account');
},
refresh: function (frm) {
frappe.require("assets/js/bank-reconciliation-tool.min.js", () =>
frm.trigger("make_reconciliation_tool")
@@ -51,7 +55,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
bank_account: function (frm) {
frappe.db.get_value(
"Bank Account",
frm.bank_account,
frm.doc.bank_account,
"account",
(r) => {
frappe.db.get_value(
@@ -60,6 +64,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
"account_currency",
(r) => {
frm.currency = r.account_currency;
frm.trigger("render_chart");
}
);
}
@@ -124,7 +129,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}
},
render_chart(frm) {
render_chart: frappe.utils.debounce((frm) => {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
{
$reconciliation_tool_cards: frm.get_field(
@@ -136,7 +141,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
currency: frm.currency,
}
);
},
}, 500),
render(frm) {
if (frm.doc.bank_account) {

View File

@@ -7,6 +7,7 @@ import json
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt
from erpnext import get_company_currency
@@ -21,48 +22,63 @@ from erpnext.accounts.utils import get_balance_on
class BankReconciliationTool(Document):
pass
@frappe.whitelist()
def get_bank_transactions(bank_account, from_date = None, to_date = None):
def get_bank_transactions(bank_account, from_date=None, to_date=None):
# returns bank transactions for a bank account
filters = []
filters.append(['bank_account', '=', bank_account])
filters.append(['docstatus', '=', 1])
filters.append(['unallocated_amount', '>', 0])
filters.append(["bank_account", "=", bank_account])
filters.append(["docstatus", "=", 1])
filters.append(["unallocated_amount", ">", 0])
if to_date:
filters.append(['date', '<=', to_date])
filters.append(["date", "<=", to_date])
if from_date:
filters.append(['date', '>=', from_date])
filters.append(["date", ">=", from_date])
transactions = frappe.get_all(
'Bank Transaction',
fields = ['date', 'deposit', 'withdrawal', 'currency',
'description', 'name', 'bank_account', 'company',
'unallocated_amount', 'reference_number', 'party_type', 'party'],
filters = filters
"Bank Transaction",
fields=[
"date",
"deposit",
"withdrawal",
"currency",
"description",
"name",
"bank_account",
"company",
"unallocated_amount",
"reference_number",
"party_type",
"party",
],
filters=filters,
)
return transactions
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
account = frappe.db.get_value('Bank Account', bank_account, 'account')
filters = frappe._dict({
"account": account,
"report_date": till_date,
"include_pos_transactions": 1
})
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
total_debit, total_credit = 0,0
total_debit, total_credit = 0, 0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
bank_bal = (
flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
)
return bank_bal
@@ -75,71 +91,94 @@ def update_bank_transaction(bank_transaction_name, reference_number, party_type=
bank_transaction.party_type = party_type
bank_transaction.party = party
bank_transaction.save()
return frappe.db.get_all('Bank Transaction',
filters={
'name': bank_transaction_name
},
fields=['date', 'deposit', 'withdrawal', 'currency',
'description', 'name', 'bank_account', 'company',
'unallocated_amount', 'reference_number',
'party_type', 'party'],
return frappe.db.get_all(
"Bank Transaction",
filters={"name": bank_transaction_name},
fields=[
"date",
"deposit",
"withdrawal",
"currency",
"description",
"name",
"bank_account",
"company",
"unallocated_amount",
"reference_number",
"party_type",
"party",
],
)[0]
@frappe.whitelist()
def create_journal_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, posting_date=None, entry_type=None,
second_account=None, mode_of_payment=None, party_type=None, party=None, allow_edit=None):
def create_journal_entry_bts(
bank_transaction_name,
reference_number=None,
reference_date=None,
posting_date=None,
entry_type=None,
second_account=None,
mode_of_payment=None,
party_type=None,
party=None,
allow_edit=None,
):
# Create a new journal entry based on the bank transaction
bank_transaction = frappe.db.get_values(
"Bank Transaction", bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"] ,
as_dict=True
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True,
)[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
account_type = frappe.db.get_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (party_type and party):
frappe.throw(_("Party Type and Party is required for Receivable / Payable account {0}").format( second_account))
frappe.throw(
_("Party Type and Party is required for Receivable / Payable account {0}").format(
second_account
)
)
accounts = []
# Multi Currency?
accounts.append({
accounts.append(
{
"account": second_account,
"credit_in_account_currency": bank_transaction.deposit
if bank_transaction.deposit > 0
else 0,
"debit_in_account_currency":bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"party_type":party_type,
"party":party,
})
"credit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
"debit_in_account_currency": bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"party_type": party_type,
"party": party,
}
)
accounts.append({
accounts.append(
{
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"debit_in_account_currency":bank_transaction.deposit
if bank_transaction.deposit > 0
else 0,
})
if bank_transaction.withdrawal > 0
else 0,
"debit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
}
)
company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type" : entry_type,
"company" : company,
"posting_date" : posting_date,
"cheque_date" : reference_date,
"cheque_no" : reference_number,
"mode_of_payment" : mode_of_payment
"voucher_type": entry_type,
"company": company,
"posting_date": posting_date,
"cheque_date": reference_date,
"cheque_no": reference_number,
"mode_of_payment": mode_of_payment,
}
journal_entry = frappe.new_doc('Journal Entry')
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts)
if allow_edit:
return journal_entry
@@ -151,21 +190,32 @@ def create_journal_entry_bts( bank_transaction_name, reference_number=None, refe
else:
paid_amount = bank_transaction.withdrawal
vouchers = json.dumps([{
"payment_doctype":"Journal Entry",
"payment_name":journal_entry.name,
"amount":paid_amount}])
vouchers = json.dumps(
[{"payment_doctype": "Journal Entry", "payment_name": journal_entry.name, "amount": paid_amount}]
)
return reconcile_vouchers(bank_transaction.name, vouchers)
@frappe.whitelist()
def create_payment_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, party_type=None, party=None, posting_date=None,
mode_of_payment=None, project=None, cost_center=None, allow_edit=None):
def create_payment_entry_bts(
bank_transaction_name,
reference_number=None,
reference_date=None,
party_type=None,
party=None,
posting_date=None,
mode_of_payment=None,
project=None,
cost_center=None,
allow_edit=None,
):
# Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values(
"Bank Transaction", bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account"] ,
as_dict=True
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
@@ -173,27 +223,26 @@ def create_payment_entry_bts( bank_transaction_name, reference_number=None, refe
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company" : company,
"payment_type" : payment_type,
"reference_no" : reference_number,
"reference_date" : reference_date,
"party_type" : party_type,
"party" : party,
"posting_date" : posting_date,
"company": company,
"payment_type": payment_type,
"reference_no": reference_number,
"reference_date": reference_date,
"party_type": party_type,
"party": party,
"posting_date": posting_date,
"paid_amount": paid_amount,
"received_amount": paid_amount
"received_amount": paid_amount,
}
payment_entry = frappe.new_doc("Payment Entry")
payment_entry.update(payment_entry_dict)
if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment
payment_entry.mode_of_payment = mode_of_payment
if project:
payment_entry.project = project
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center
payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
@@ -207,80 +256,111 @@ def create_payment_entry_bts( bank_transaction_name, reference_number=None, refe
payment_entry.insert()
payment_entry.submit()
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment_entry.name,
"amount":paid_amount}])
vouchers = json.dumps(
[{"payment_doctype": "Payment Entry", "payment_name": payment_entry.name, "amount": paid_amount}]
)
return reconcile_vouchers(bank_transaction.name, vouchers)
@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
company_account = frappe.db.get_value('Bank Account', transaction.bank_account, 'account')
company_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
total_amount = 0
for voucher in vouchers:
voucher['payment_entry'] = frappe.get_doc(voucher['payment_doctype'], voucher['payment_name'])
total_amount += get_paid_amount(frappe._dict({
'payment_document': voucher['payment_doctype'],
'payment_entry': voucher['payment_name'],
}), transaction.currency, company_account)
voucher["payment_entry"] = frappe.get_doc(voucher["payment_doctype"], voucher["payment_name"])
total_amount += get_paid_amount(
frappe._dict(
{
"payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"],
}
),
transaction.currency,
company_account,
)
if total_amount > transaction.unallocated_amount:
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction"))
frappe.throw(
_(
"The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"
)
)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers:
gl_entry = frappe.db.get_value("GL Entry", dict(account=account, voucher_type=voucher['payment_doctype'], voucher_no=voucher['payment_name']), ['credit', 'debit'], as_dict=1)
gl_amount, transaction_amount = (gl_entry.credit, transaction.deposit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.withdrawal)
gl_entry = frappe.db.get_value(
"GL Entry",
dict(
account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
),
["credit", "debit"],
as_dict=1,
)
gl_amount, transaction_amount = (
(gl_entry.credit, transaction.deposit)
if gl_entry.credit > 0
else (gl_entry.debit, transaction.withdrawal)
)
allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
transaction.append("payment_entries", {
"payment_document": voucher['payment_entry'].doctype,
"payment_entry": voucher['payment_entry'].name,
"allocated_amount": allocated_amount
})
transaction.append(
"payment_entries",
{
"payment_document": voucher["payment_entry"].doctype,
"payment_entry": voucher["payment_entry"].name,
"allocated_amount": allocated_amount,
},
)
transaction.save()
transaction.update_allocations()
return frappe.get_doc("Bank Transaction", bank_transaction_name)
@frappe.whitelist()
def get_linked_payments(bank_transaction_name, document_types = None):
def get_linked_payments(bank_transaction_name, document_types=None):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_account = frappe.db.get_values(
"Bank Account",
transaction.bank_account,
["account", "company"],
as_dict=True)[0]
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0]
(account, company) = (bank_account.account, bank_account.company)
matching = check_matching(account, company, transaction, document_types)
return matching
def check_matching(bank_account, company, transaction, document_types):
# combine all types of vouchers
subquery = get_queries(bank_account, company, transaction, document_types)
filters = {
"amount": transaction.unallocated_amount,
"payment_type" : "Receive" if transaction.deposit > 0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
"bank_account": bank_account
}
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
"bank_account": bank_account,
}
matching_vouchers = []
matching_vouchers.extend(get_loan_vouchers(bank_account, transaction, document_types, filters))
for query in subquery:
matching_vouchers.extend(
frappe.db.sql(query, filters,)
frappe.db.sql(
query,
filters,
)
)
return sorted(matching_vouchers, key = lambda x: x[0], reverse=True) if matching_vouchers else []
return sorted(matching_vouchers, key=lambda x: x[0], reverse=True) if matching_vouchers else []
def get_queries(bank_account, company, transaction, document_types):
# get queries to get matching vouchers
@@ -297,7 +377,7 @@ def get_queries(bank_account, company, transaction, document_types):
queries.extend([je_amount_matching])
if transaction.deposit > 0 and "sales_invoice" in document_types:
si_amount_matching = get_si_matching_query(amount_condition)
si_amount_matching = get_si_matching_query(amount_condition)
queries.extend([si_amount_matching])
if transaction.withdrawal > 0:
@@ -311,13 +391,103 @@ def get_queries(bank_account, company, transaction, document_types):
return queries
def get_loan_vouchers(bank_account, transaction, document_types, filters):
vouchers = []
amount_condition = True if "exact_match" in document_types else False
if transaction.withdrawal > 0 and "loan_disbursement" in document_types:
vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters))
if transaction.deposit > 0 and "loan_repayment" in document_types:
vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters))
return vouchers
def get_ld_matching_query(bank_account, amount_condition, filters):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
matching_party = loan_disbursement.applicant_type == filters.get(
"party_type"
) and loan_disbursement.applicant == filters.get("party")
rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
rank1 = frappe.qb.terms.Case().when(matching_party, 1).else_(0)
query = (
frappe.qb.from_(loan_disbursement)
.select(
rank + rank1 + 1,
ConstantColumn("Loan Disbursement").as_("doctype"),
loan_disbursement.name,
loan_disbursement.disbursed_amount,
loan_disbursement.reference_number,
loan_disbursement.reference_date,
loan_disbursement.applicant_type,
loan_disbursement.disbursement_date,
)
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.clearance_date.isnull())
.where(loan_disbursement.disbursement_account == bank_account)
)
if amount_condition:
query.where(loan_disbursement.disbursed_amount == filters.get("amount"))
else:
query.where(loan_disbursement.disbursed_amount <= filters.get("amount"))
vouchers = query.run(as_list=True)
return vouchers
def get_lr_matching_query(bank_account, amount_condition, filters):
loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number")
matching_party = loan_repayment.applicant_type == filters.get(
"party_type"
) and loan_repayment.applicant == filters.get("party")
rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
rank1 = frappe.qb.terms.Case().when(matching_party, 1).else_(0)
query = (
frappe.qb.from_(loan_repayment)
.select(
rank + rank1 + 1,
ConstantColumn("Loan Repayment").as_("doctype"),
loan_repayment.name,
loan_repayment.amount_paid,
loan_repayment.reference_number,
loan_repayment.reference_date,
loan_repayment.applicant_type,
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.payment_account == bank_account)
)
if amount_condition:
query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
query.where(loan_repayment.amount_paid <= filters.get("amount"))
vouchers = query.run()
return vouchers
def get_pe_matching_query(amount_condition, account_from_to, transaction):
# get matching payment entries query
if transaction.deposit > 0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
return f"""
return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
@@ -348,7 +518,6 @@ def get_je_matching_query(amount_condition, transaction):
# We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
return f"""
@@ -407,6 +576,7 @@ def get_si_matching_query(amount_condition):
AND si.docstatus = 1
"""
def get_pi_matching_query(amount_condition):
# get matching purchase invoice query
return f"""
@@ -432,11 +602,16 @@ def get_pi_matching_query(amount_condition):
AND cash_bank_account = %(bank_account)s
"""
def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query
mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
mode_of_payments = [
x["parent"]
for x in frappe.db.get_all(
"Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"]
)
]
mode_of_payments = "('" + "', '".join(mode_of_payments) + "' )"
company_currency = get_company_currency(company)
return f"""
SELECT

View File

@@ -19,6 +19,7 @@ from six import string_types
INVALID_VALUES = ("", None)
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
super(BankStatementImport, self).__init__(*args, **kwargs)
@@ -50,16 +51,14 @@ class BankStatementImport(DataImport):
self.import_file, self.google_sheets_url
)
if 'Bank Account' not in json.dumps(preview['columns']):
if "Bank Account" not in json.dumps(preview["columns"]):
frappe.throw(_("Please add the Bank Account column"))
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(
_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")
)
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
enqueued_jobs = [d.get("job_name") for d in get_info()]
@@ -82,21 +81,25 @@ class BankStatementImport(DataImport):
return False
@frappe.whitelist()
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
import_file, google_sheets_url
)
@frappe.whitelist()
def form_start_import(data_import):
return frappe.get_doc("Bank Statement Import", data_import).start_import()
@frappe.whitelist()
def download_errored_template(data_import_name):
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
def parse_data_from_template(raw_data):
data = []
@@ -109,7 +112,10 @@ def parse_data_from_template(raw_data):
return data
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
def start_import(
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
):
"""This method runs in background job"""
update_mapping_db(bank, template_options)
@@ -117,7 +123,7 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
data_import = frappe.get_doc("Bank Statement Import", data_import)
file = import_file_path if import_file_path else google_sheets_url
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
data = parse_data_from_template(import_file.raw_data)
@@ -137,16 +143,18 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name})
def update_mapping_db(bank, template_options):
bank = frappe.get_doc("Bank", bank)
for d in bank.bank_transaction_mapping:
d.delete()
for d in json.loads(template_options)["column_to_field_map"].items():
bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1] ,"file_field": d[0]} )
bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1], "file_field": d[0]})
bank.save()
def add_bank_account(data, bank_account):
bank_account_loc = None
if "Bank Account" not in data[0]:
@@ -162,6 +170,7 @@ def add_bank_account(data, bank_account):
else:
row.append(bank_account)
def write_files(import_file, data):
full_file_path = import_file.file_doc.get_full_path()
parts = import_file.file_doc.get_extension()
@@ -169,11 +178,12 @@ def write_files(import_file, data):
extension = extension.lstrip(".")
if extension == "csv":
with open(full_file_path, 'w', newline='') as file:
with open(full_file_path, "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(data)
elif extension == "xlsx" or "xls":
write_xlsx(data, "trans", file_path = full_file_path)
write_xlsx(data, "trans", file_path=full_file_path)
def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
# from xlsx utils with changes
@@ -188,19 +198,21 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
ws.column_dimensions[get_column_letter(i + 1)].width = column_width
row1 = ws.row_dimensions[1]
row1.font = Font(name='Calibri', bold=True)
row1.font = Font(name="Calibri", bold=True)
for row in data:
clean_row = []
for item in row:
if isinstance(item, string_types) and (sheet_name not in ['Data Import Template', 'Data Export']):
if isinstance(item, string_types) and (
sheet_name not in ["Data Import Template", "Data Export"]
):
value = handle_html(item)
else:
value = item
if isinstance(item, string_types) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
# Remove illegal characters from the string
value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
value = re.sub(ILLEGAL_CHARACTERS_RE, "", value)
clean_row.append(value)
@@ -209,19 +221,20 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
wb.save(file_path)
return True
@frappe.whitelist()
def upload_bank_statement(**args):
args = frappe._dict(args)
bsi = frappe.new_doc("Bank Statement Import")
if args.company:
bsi.update({
"company": args.company,
})
bsi.update(
{
"company": args.company,
}
)
if args.bank_account:
bsi.update({
"bank_account": args.bank_account
})
bsi.update({"bank_account": args.bank_account})
return bsi

View File

@@ -28,17 +28,26 @@ class BankTransaction(StatusUpdater):
def update_allocations(self):
if self.payment_entries:
allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
allocated_amount = reduce(
lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
)
else:
allocated_amount = 0
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount))
frappe.db.set_value(
self.doctype,
self.name,
"unallocated_amount",
abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
)
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)))
frappe.db.set_value(
self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))
)
amount = self.deposit or self.withdrawal
if amount == self.allocated_amount:
@@ -48,7 +57,14 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
if payment_entry.payment_document in [
"Payment Entry",
"Journal Entry",
"Purchase Invoice",
"Expense Claim",
"Loan Repayment",
"Loan Disbursement",
]:
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document == "Sales Invoice":
@@ -56,38 +72,41 @@ class BankTransaction(StatusUpdater):
def clear_simple_entry(self, payment_entry, for_cancel=False):
if payment_entry.payment_document == "Payment Entry":
if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
if (
frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type")
== "Internal Transfer"
):
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
payment_entry.payment_document, payment_entry.payment_entry,
"clearance_date", clearance_date)
payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date
)
def clear_sales_invoice(self, payment_entry, for_cancel=False):
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
"Sales Invoice Payment",
dict(
parenttype=payment_entry.payment_document,
parent=payment_entry.payment_entry
),
"clearance_date", clearance_date)
dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
"clearance_date",
clearance_date,
)
def get_reconciled_bank_transactions(payment_entry):
reconciled_bank_transactions = frappe.get_all(
'Bank Transaction Payments',
filters = {
'payment_entry': payment_entry.payment_entry
},
fields = ['parent']
"Bank Transaction Payments",
filters={"payment_entry": payment_entry.payment_entry},
fields=["parent"],
)
return reconciled_bank_transactions
def get_total_allocated_amount(payment_entry):
return frappe.db.sql("""
return frappe.db.sql(
"""
SELECT
SUM(btp.allocated_amount) as allocated_amount,
bt.name
@@ -100,36 +119,73 @@ def get_total_allocated_amount(payment_entry):
AND
btp.payment_entry = %s
AND
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
bt.docstatus = 1""",
(payment_entry.payment_document, payment_entry.payment_entry),
as_dict=True,
)
def get_paid_amount(payment_entry, currency, bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
if payment_entry.payment_document == 'Payment Entry':
if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
paid_amount_field = ("base_paid_amount"
if doc.paid_to_account_currency == currency else "paid_amount")
return frappe.db.get_value(payment_entry.payment_document,
payment_entry.payment_entry, paid_amount_field)
if doc.payment_type == "Receive":
paid_amount_field = (
"received_amount" if doc.paid_to_account_currency == currency else "base_received_amount"
)
elif doc.payment_type == "Pay":
paid_amount_field = (
"paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount"
)
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, paid_amount_field
)
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account}, "sum(credit_in_account_currency)")
return frappe.db.get_value(
"Journal Entry Account",
{"parent": payment_entry.payment_entry, "account": bank_account},
"sum(credit_in_account_currency)",
)
elif payment_entry.payment_document == "Expense Claim":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed"
)
elif payment_entry.payment_document == "Loan Disbursement":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount"
)
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
else:
frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry))
frappe.throw(
"Please reconcile {0}: {1} manually".format(
payment_entry.payment_document, payment_entry.payment_entry
)
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname):
if frappe.db.exists(doctype, docname):
doc = frappe.get_doc(doctype, docname)
if doctype == "Sales Invoice":
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=doc.payment_document,
parent=doc.payment_entry), "clearance_date", None)
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doc.payment_document, parent=doc.payment_entry),
"clearance_date",
None,
)
else:
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)

View File

@@ -19,12 +19,14 @@ def upload_bank_statement():
fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(fcontent, False)
elif frappe.safe_encode(fname).lower().endswith("xlsx".encode('utf-8')):
elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)
columns = rows[0]
@@ -44,12 +46,10 @@ def create_bank_entries(columns, data, bank_account):
continue
fields = {}
for key, value in iteritems(header_map):
fields.update({key: d[int(value)-1]})
fields.update({key: d[int(value) - 1]})
try:
bank_transaction = frappe.get_doc({
"doctype": "Bank Transaction"
})
bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"})
bank_transaction.update(fields)
bank_transaction.date = getdate(parse_date(bank_transaction.date))
bank_transaction.bank_account = bank_account
@@ -62,6 +62,7 @@ def create_bank_entries(columns, data, bank_account):
return {"success": success, "errors": errors}
def get_header_mapping(columns, bank_account):
mapping = get_bank_mapping(bank_account)
@@ -72,10 +73,11 @@ def get_header_mapping(columns, bank_account):
return header_map
def get_bank_mapping(bank_account):
bank_name = frappe.db.get_value("Bank Account", bank_account, "bank")
bank = frappe.get_doc("Bank", bank_name)
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
mapping = {row.file_field: row.bank_transaction_field for row in bank.bank_transaction_mapping}
return mapping

View File

@@ -17,6 +17,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -41,21 +42,34 @@ class TestBankTransaction(unittest.TestCase):
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"),
)
linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount")
unallocated_amount = frappe.db.get_value(
"Bank Transaction", bank_transaction.name, "unallocated_amount"
)
self.assertTrue(unallocated_amount == 0)
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
@@ -69,122 +83,177 @@ class TestBankTransaction(unittest.TestCase):
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"),
)
linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled
def test_already_reconciled(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
self.assertRaises(frappe.ValidationError, reconcile_vouchers, bank_transaction_name=bank_transaction.name, vouchers=vouchers)
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
self.assertRaises(
frappe.ValidationError,
reconcile_vouchers,
bank_transaction_name=bank_transaction.name,
vouchers=vouchers,
)
# Raise an error if debitor transaction vs debitor payment
def test_clear_sales_invoice(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"),
)
payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"]))
vouchers = json.dumps([{
"payment_doctype":"Sales Invoice",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
vouchers = json.dumps(
[
{
"payment_doctype": "Sales Invoice",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers=vouchers)
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
self.assertEqual(
frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0
)
self.assertTrue(
frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date")
is not None
)
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
try:
frappe.get_doc({
"doctype": "Bank",
"bank_name":bank_name,
}).insert()
frappe.get_doc(
{
"doctype": "Bank",
"bank_name": bank_name,
}
).insert()
except frappe.DuplicateEntryError:
pass
try:
frappe.get_doc({
"doctype": "Bank Account",
"account_name":"Checking Account",
"bank": bank_name,
"account": account_name
}).insert()
frappe.get_doc(
{
"doctype": "Bank Account",
"account_name": "Checking Account",
"bank": bank_name,
"account": account_name,
}
).insert()
except frappe.DuplicateEntryError:
pass
def add_transactions():
create_bank_account()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": "2018-10-23",
"deposit": 1200,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": "2018-10-23",
"deposit": 1200,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
"date": "2018-10-23",
"deposit": 1700,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
"date": "2018-10-23",
"deposit": 1700,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
"date": "2018-10-26",
"withdrawal": 690,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
"date": "2018-10-26",
"withdrawal": 690,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
"date": "2018-10-27",
"deposit": 3900,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
"date": "2018-10-27",
"deposit": 3900,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
"date": "2018-10-27",
"withdrawal": 109080,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
"date": "2018-10-27",
"withdrawal": 109080,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
def add_vouchers():
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Conrad Electronic"
}).insert()
frappe.get_doc(
{
"doctype": "Supplier",
"supplier_group": "All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Conrad Electronic",
}
).insert()
except frappe.DuplicateEntryError:
pass
@@ -198,12 +267,14 @@ def add_vouchers():
pe.submit()
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Mr G"
}).insert()
frappe.get_doc(
{
"doctype": "Supplier",
"supplier_group": "All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Mr G",
}
).insert()
except frappe.DuplicateEntryError:
pass
@@ -222,26 +293,30 @@ def add_vouchers():
pe.submit()
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Poore Simon's"
}).insert()
frappe.get_doc(
{
"doctype": "Supplier",
"supplier_group": "All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Poore Simon's",
}
).insert()
except frappe.DuplicateEntryError:
pass
try:
frappe.get_doc({
"doctype": "Customer",
"customer_group":"All Customer Groups",
"customer_type": "Company",
"customer_name": "Poore Simon's"
}).insert()
frappe.get_doc(
{
"doctype": "Customer",
"customer_group": "All Customer Groups",
"customer_type": "Company",
"customer_name": "Poore Simon's",
}
).insert()
except frappe.DuplicateEntryError:
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save =1)
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = "_Test Bank - _TC"
pi.insert()
pi.submit()
@@ -261,33 +336,31 @@ def add_vouchers():
pe.submit()
try:
frappe.get_doc({
"doctype": "Customer",
"customer_group":"All Customer Groups",
"customer_type": "Company",
"customer_name": "Fayva"
}).insert()
frappe.get_doc(
{
"doctype": "Customer",
"customer_group": "All Customer Groups",
"customer_type": "Company",
"customer_name": "Fayva",
}
).insert()
except frappe.DuplicateEntryError:
pass
mode_of_payment = frappe.get_doc({
"doctype": "Mode of Payment",
"name": "Cash"
})
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
if not frappe.db.get_value('Mode of Payment Account', {'company': "_Test Company", 'parent': "Cash"}):
mode_of_payment.append("accounts", {
"company": "_Test Company",
"default_account": "_Test Bank - _TC"
})
if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
si.append("payments", {
"mode_of_payment": "Cash",
"account": "_Test Bank - _TC",
"amount": 109080
})
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.insert()
si.submit()

View File

@@ -14,13 +14,19 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.utils import get_fiscal_year
class BudgetError(frappe.ValidationError): pass
class DuplicateBudgetError(frappe.ValidationError): pass
class BudgetError(frappe.ValidationError):
pass
class DuplicateBudgetError(frappe.ValidationError):
pass
class Budget(Document):
def autoname(self):
self.name = make_autoname(self.get(frappe.scrub(self.budget_against))
+ "/" + self.fiscal_year + "/.###")
self.name = make_autoname(
self.get(frappe.scrub(self.budget_against)) + "/" + self.fiscal_year + "/.###"
)
def validate(self):
if not self.get(frappe.scrub(self.budget_against)):
@@ -35,34 +41,44 @@ class Budget(Document):
budget_against = self.get(budget_against_field)
accounts = [d.account for d in self.accounts] or []
existing_budget = frappe.db.sql("""
existing_budget = frappe.db.sql(
"""
select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
% ('%s', budget_against_field, '%s', '%s', '%s', ','.join(['%s'] * len(accounts))),
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts), as_dict=1)
% ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
as_dict=1,
)
for d in existing_budget:
frappe.throw(_("Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4}")
.format(d.name, self.budget_against, budget_against, d.account, self.fiscal_year), DuplicateBudgetError)
frappe.throw(
_(
"Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4}"
).format(d.name, self.budget_against, budget_against, d.account, self.fiscal_year),
DuplicateBudgetError,
)
def validate_accounts(self):
account_list = []
for d in self.get('accounts'):
for d in self.get("accounts"):
if d.account:
account_details = frappe.db.get_value("Account", d.account,
["is_group", "company", "report_type"], as_dict=1)
account_details = frappe.db.get_value(
"Account", d.account, ["is_group", "company", "report_type"], as_dict=1
)
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
frappe.throw(_("Account {0} does not belongs to company {1}")
.format(d.account, self.company))
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
elif account_details.report_type != "Profit and Loss":
frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Income or Expense account")
.format(d.account))
frappe.throw(
_("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
d.account
)
)
if d.account in account_list:
frappe.throw(_("Account {0} has been entered multiple times").format(d.account))
@@ -70,51 +86,66 @@ class Budget(Document):
account_list.append(d.account)
def set_null_value(self):
if self.budget_against == 'Cost Center':
if self.budget_against == "Cost Center":
self.project = None
else:
self.cost_center = None
def validate_applicable_for(self):
if (self.applicable_on_material_request
and not (self.applicable_on_purchase_order and self.applicable_on_booking_actual_expenses)):
frappe.throw(_("Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses"))
if self.applicable_on_material_request and not (
self.applicable_on_purchase_order and self.applicable_on_booking_actual_expenses
):
frappe.throw(
_("Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses")
)
elif (self.applicable_on_purchase_order
and not (self.applicable_on_booking_actual_expenses)):
elif self.applicable_on_purchase_order and not (self.applicable_on_booking_actual_expenses):
frappe.throw(_("Please enable Applicable on Booking Actual Expenses"))
elif not(self.applicable_on_material_request
or self.applicable_on_purchase_order or self.applicable_on_booking_actual_expenses):
elif not (
self.applicable_on_material_request
or self.applicable_on_purchase_order
or self.applicable_on_booking_actual_expenses
):
self.applicable_on_booking_actual_expenses = 1
def validate_expense_against_budget(args):
args = frappe._dict(args)
if args.get('company') and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get('posting_date'), company=args.get('company'))[0]
frappe.flags.exception_approver_role = frappe.get_cached_value('Company',
args.get('company'), 'exception_budget_approver_role')
if args.get("company") and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
frappe.flags.exception_approver_role = frappe.get_cached_value(
"Company", args.get("company"), "exception_budget_approver_role"
)
if not args.account:
args.account = args.get("expense_account")
if not (args.get('account') and args.get('cost_center')) and args.item_code:
if not (args.get("account") and args.get("cost_center")) and args.item_code:
args.cost_center, args.account = get_item_details(args)
if not args.account:
return
for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
if (args.get(budget_against) and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
for budget_against in ["project", "cost_center"] + get_accounting_dimensions():
if (
args.get(budget_against)
and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
):
doctype = frappe.unscrub(budget_against)
if frappe.get_cached_value('DocType', doctype, 'is_tree'):
if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
where lft<=%s and rgt>=%s and name=b.%s)""" % (
doctype,
lft,
rgt,
budget_against,
) # nosec
args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
@@ -123,7 +154,8 @@ def validate_expense_against_budget(args):
args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql("""
budget_records = frappe.db.sql(
"""
select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request,
@@ -138,11 +170,17 @@ def validate_expense_against_budget(args):
b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1
{condition}
""".format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
""".format(
condition=condition, budget_against_field=budget_against
),
(args.fiscal_year, args.account),
as_dict=True,
) # nosec
if budget_records:
validate_budget_records(args, budget_records)
def validate_budget_records(args, budget_records):
for budget in budget_records:
if flt(budget.budget_amount):
@@ -150,88 +188,118 @@ def validate_budget_records(args, budget_records):
yearly_action, monthly_action = get_actions(args, budget)
if monthly_action in ["Stop", "Warn"]:
budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution,
args.posting_date, args.fiscal_year, budget.budget_amount)
budget_amount = get_accumulated_monthly_budget(
budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount
)
args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(args, budget_amount,
_("Accumulated Monthly"), monthly_action, budget.budget_against, amount)
compare_expense_with_budget(
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
)
if (
yearly_action in ("Stop", "Warn")
and monthly_action != "Stop"
and yearly_action != monthly_action
):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
and yearly_action != monthly_action:
compare_expense_with_budget(args, flt(budget.budget_amount),
_("Annual"), yearly_action, budget.budget_against, amount)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
actual_expense = amount or get_actual_expense(args)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
currency = frappe.get_cached_value('Company', args.company, 'default_currency')
currency = frappe.get_cached_value("Company", args.company, "default_currency")
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
_(action_for), frappe.bold(args.account), args.budget_against_field,
frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)),
frappe.bold(fmt_money(diff, currency=currency)))
_(action_for),
frappe.bold(args.account),
args.budget_against_field,
frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)),
frappe.bold(fmt_money(diff, currency=currency)),
)
if (frappe.flags.exception_approver_role
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)):
if (
frappe.flags.exception_approver_role
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
):
action = "Warn"
if action=="Stop":
if action == "Stop":
frappe.throw(msg, BudgetError)
else:
frappe.msgprint(msg, indicator='orange')
frappe.msgprint(msg, indicator="orange")
def get_actions(args, budget):
yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
if args.get('doctype') == 'Material Request' and budget.for_material_request:
if args.get("doctype") == "Material Request" and budget.for_material_request:
yearly_action = budget.action_if_annual_budget_exceeded_on_mr
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_mr
elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order:
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
yearly_action = budget.action_if_annual_budget_exceeded_on_po
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_po
return yearly_action, monthly_action
def get_amount(args, budget):
amount = 0
if args.get('doctype') == 'Material Request' and budget.for_material_request:
amount = (get_requested_amount(args, budget)
+ get_ordered_amount(args, budget) + get_actual_expense(args))
if args.get("doctype") == "Material Request" and budget.for_material_request:
amount = (
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
)
elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order:
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
return amount
def get_requested_amount(args, budget):
item_code = args.get('item_code')
condition = get_other_condition(args, budget, 'Material Request')
data = frappe.db.sql(""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
def get_requested_amount(args, budget):
item_code = args.get("item_code")
condition = get_other_condition(args, budget, "Material Request")
data = frappe.db.sql(
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition), item_code, as_list=1)
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
condition
),
item_code,
as_list=1,
)
return data[0][0] if data else 0
def get_ordered_amount(args, budget):
item_code = args.get('item_code')
condition = get_other_condition(args, budget, 'Purchase Order')
item_code = args.get("item_code")
condition = get_other_condition(args, budget, "Purchase Order")
data = frappe.db.sql(""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
data = frappe.db.sql(
""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and parent.status != 'Closed' and {0}""".format(condition), item_code, as_list=1)
and parent.status != 'Closed' and {0}""".format(
condition
),
item_code,
as_list=1,
)
return data[0][0] if data else 0
def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = args.get("budget_against_field")
@@ -239,41 +307,51 @@ def get_other_condition(args, budget, for_doc):
if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
if args.get('fiscal_year'):
date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date'
start_date, end_date = frappe.db.get_value('Fiscal Year', args.get('fiscal_year'),
['year_start_date', 'year_end_date'])
if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
start_date, end_date = frappe.db.get_value(
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
condition += """ and parent.%s
between '%s' and '%s' """ %(date_field, start_date, end_date)
between '%s' and '%s' """ % (
date_field,
start_date,
end_date,
)
return condition
def get_actual_expense(args):
if not args.budget_against_doctype:
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
budget_against_field = args.get('budget_against_field')
condition1 = " and gle.posting_date <= %(month_end_date)s" \
if args.get("month_end_date") else ""
budget_against_field = args.get("budget_against_field")
condition1 = " and gle.posting_date <= %(month_end_date)s" if args.get("month_end_date") else ""
if args.is_tree:
lft_rgt = frappe.db.get_value(args.budget_against_doctype,
args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
lft_rgt = frappe.db.get_value(
args.budget_against_doctype, args.get(budget_against_field), ["lft", "rgt"], as_dict=1
)
args.update(lft_rgt)
condition2 = """and exists(select name from `tab{doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
budget_against_field=budget_against_field)
and name=gle.{budget_against_field})""".format(
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
)
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
budget_against = budget_against_field)
gle.{budget_against} = %({budget_against})s)""".format(
doctype=args.budget_against_doctype, budget_against=budget_against_field
)
amount = flt(frappe.db.sql("""
amount = flt(
frappe.db.sql(
"""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where gle.account=%(account)s
@@ -282,46 +360,59 @@ def get_actual_expense(args):
and gle.company=%(company)s
and gle.docstatus=1
{condition2}
""".format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
""".format(
condition1=condition1, condition2=condition2
),
(args),
)[0][0]
) # nosec
return amount
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
for d in frappe.db.sql("""select mdp.month, mdp.percentage_allocation
for d in frappe.db.sql(
"""select mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""", fiscal_year, as_dict=1):
distribution.setdefault(d.month, d.percentage_allocation)
where mdp.parent=md.name and md.fiscal_year=%s""",
fiscal_year,
as_dict=1,
):
distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date")
accumulated_percentage = 0.0
while(dt <= getdate(posting_date)):
while dt <= getdate(posting_date):
if monthly_distribution:
accumulated_percentage += distribution.get(getdate(dt).strftime("%B"), 0)
else:
accumulated_percentage += 100.0/12
accumulated_percentage += 100.0 / 12
dt = add_months(dt, 1)
return annual_budget * accumulated_percentage / 100
def get_item_details(args):
cost_center, expense_account = None, None
if not args.get('company'):
if not args.get("company"):
return cost_center, expense_account
if args.item_code:
item_defaults = frappe.db.get_value('Item Default',
{'parent': args.item_code, 'company': args.get('company')},
['buying_cost_center', 'expense_account'])
item_defaults = frappe.db.get_value(
"Item Default",
{"parent": args.item_code, "company": args.get("company")},
["buying_cost_center", "expense_account"],
)
if item_defaults:
cost_center, expense_account = item_defaults
if not (cost_center and expense_account):
for doctype in ['Item Group', 'Company']:
for doctype in ["Item Group", "Company"]:
data = get_expense_cost_center(doctype, args)
if not cost_center and data:
@@ -335,11 +426,15 @@ def get_item_details(args):
return cost_center, expense_account
def get_expense_cost_center(doctype, args):
if doctype == 'Item Group':
return frappe.db.get_value('Item Default',
{'parent': args.get(frappe.scrub(doctype)), 'company': args.get('company')},
['buying_cost_center', 'expense_account'])
if doctype == "Item Group":
return frappe.db.get_value(
"Item Default",
{"parent": args.get(frappe.scrub(doctype)), "company": args.get("company")},
["buying_cost_center", "expense_account"],
)
else:
return frappe.db.get_value(doctype, args.get(frappe.scrub(doctype)),\
['cost_center', 'default_expense_account'])
return frappe.db.get_value(
doctype, args.get(frappe.scrub(doctype)), ["cost_center", "default_expense_account"]
)

View File

@@ -11,7 +11,8 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
test_dependencies = ['Monthly Distribution']
test_dependencies = ["Monthly Distribution"]
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
@@ -19,11 +20,18 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
submit=True,
)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
self.assertTrue(
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
budget.cancel()
jv.cancel()
@@ -33,10 +41,17 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -48,49 +63,65 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
frappe.db.set_value('Company', budget.company, 'exception_budget_approver_role', 'Accounts User')
frappe.db.set_value("Company", budget.company, "exception_budget_approver_role", "Accounts User")
jv.submit()
self.assertEqual(frappe.db.get_value('Journal Entry', jv.name, 'docstatus'), 1)
self.assertEqual(frappe.db.get_value("Journal Entry", jv.name, "docstatus"), 1)
jv.cancel()
frappe.db.set_value('Company', budget.company, 'exception_budget_approver_role', '')
frappe.db.set_value("Company", budget.company, "exception_budget_approver_role", "")
budget.load_from_db()
budget.cancel()
def test_monthly_budget_crossed_for_mr(self):
budget = make_budget(applicable_on_material_request=1,
applicable_on_purchase_order=1, action_if_accumulated_monthly_budget_exceeded_on_mr="Stop",
budget_against="Cost Center")
budget = make_budget(
applicable_on_material_request=1,
applicable_on_purchase_order=1,
action_if_accumulated_monthly_budget_exceeded_on_mr="Stop",
budget_against="Cost Center",
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
mr = frappe.get_doc({
"doctype": "Material Request",
"material_request_type": "Purchase",
"transaction_date": nowdate(),
"company": budget.company,
"items": [{
'item_code': '_Test Item',
'qty': 1,
'uom': "_Test UOM",
'warehouse': '_Test Warehouse - _TC',
'schedule_date': nowdate(),
'rate': 100000,
'expense_account': '_Test Account Cost for Goods Sold - _TC',
'cost_center': '_Test Cost Center - _TC'
}]
})
mr = frappe.get_doc(
{
"doctype": "Material Request",
"material_request_type": "Purchase",
"transaction_date": nowdate(),
"company": budget.company,
"items": [
{
"item_code": "_Test Item",
"qty": 1,
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
"schedule_date": nowdate(),
"rate": 100000,
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
}
],
}
)
mr.set_missing_values()
@@ -100,11 +131,16 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_crossed_for_po(self):
budget = make_budget(applicable_on_purchase_order=1,
action_if_accumulated_monthly_budget_exceeded_on_po="Stop", budget_against="Cost Center")
budget = make_budget(
applicable_on_purchase_order=1,
action_if_accumulated_monthly_budget_exceeded_on_po="Stop",
budget_against="Cost Center",
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
@@ -122,12 +158,20 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
"_Test Cost Center - _TC",
project=project,
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -139,8 +183,13 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
250000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -153,9 +202,14 @@ class TestBudget(unittest.TestCase):
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
project=project, posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
250000,
"_Test Cost Center - _TC",
project=project,
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -169,14 +223,23 @@ class TestBudget(unittest.TestCase):
if month > 9:
month = 9
for i in range(month+1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
for i in range(month + 1):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
20000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
submit=True,
)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
self.assertTrue(
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel)
@@ -193,14 +256,23 @@ class TestBudget(unittest.TestCase):
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for i in range(month + 1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
project=project)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
20000,
"_Test Cost Center - _TC",
posting_date=nowdate(),
submit=True,
project=project,
)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
self.assertTrue(
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel)
@@ -212,10 +284,17 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
"_Test Cost Center 2 - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -226,19 +305,28 @@ class TestBudget(unittest.TestCase):
cost_center = "_Test Cost Center 3 - _TC"
if not frappe.db.exists("Cost Center", cost_center):
frappe.get_doc({
'doctype': 'Cost Center',
'cost_center_name': '_Test Cost Center 3',
'parent_cost_center': "_Test Company - _TC",
'company': '_Test Company',
'is_group': 0
}).insert(ignore_permissions=True)
frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "_Test Cost Center 3",
"parent_cost_center": "_Test Company - _TC",
"company": "_Test Company",
"is_group": 0,
}
).insert(ignore_permissions=True)
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, cost_center, posting_date=nowdate())
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
cost_center,
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
@@ -255,14 +343,16 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
fiscal_year = get_fiscal_year(nowdate())[0]
args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
"fiscal_year": fiscal_year,
"budget_against_field": budget_against_field,
})
args = frappe._dict(
{
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
"fiscal_year": fiscal_year,
"budget_against_field": budget_against_field,
}
)
if not args.get(budget_against_field):
args[budget_against_field] = budget_against
@@ -271,26 +361,42 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
if existing_expense:
if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
-existing_expense,
"_Test Cost Center - _TC",
posting_date=nowdate(),
submit=True,
)
elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
-existing_expense,
"_Test Cost Center - _TC",
submit=True,
project=budget_against,
posting_date=nowdate(),
)
def make_budget(**args):
args = frappe._dict(args)
budget_against=args.budget_against
cost_center=args.cost_center
budget_against = args.budget_against
cost_center = args.cost_center
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project":
project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)})
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)})
else:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
budget_list = frappe.get_all(
"Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
)
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
@@ -300,7 +406,7 @@ def make_budget(**args):
if budget_against == "Project":
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
else:
budget.cost_center =cost_center or "_Test Cost Center - _TC"
budget.cost_center = cost_center or "_Test Cost Center - _TC"
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
@@ -312,20 +418,27 @@ def make_budget(**args):
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.budget_against = budget_against
budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 200000
})
budget.append(
"accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
)
if args.applicable_on_material_request:
budget.applicable_on_material_request = 1
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or 'Warn'
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = args.action_if_accumulated_monthly_budget_exceeded_on_mr or 'Warn'
budget.action_if_annual_budget_exceeded_on_mr = (
args.action_if_annual_budget_exceeded_on_mr or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
)
if args.applicable_on_purchase_order:
budget.applicable_on_purchase_order = 1
budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or 'Warn'
budget.action_if_accumulated_monthly_budget_exceeded_on_po = args.action_if_accumulated_monthly_budget_exceeded_on_po or 'Warn'
budget.action_if_annual_budget_exceeded_on_po = (
args.action_if_annual_budget_exceeded_on_po or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
)
budget.insert()
budget.submit()

View File

@@ -11,28 +11,42 @@ from frappe.utils import flt
class CForm(Document):
def validate(self):
"""Validate invoice that c-form is applicable
and no other c-form is received for that"""
and no other c-form is received for that"""
for d in self.get('invoices'):
for d in self.get("invoices"):
if d.invoice_no:
inv = frappe.db.sql("""select c_form_applicable, c_form_no from
`tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no)
inv = frappe.db.sql(
"""select c_form_applicable, c_form_no from
`tabSales Invoice` where name = %s and docstatus = 1""",
d.invoice_no,
)
if inv and inv[0][0] != 'Yes':
if inv and inv[0][0] != "Yes":
frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
elif inv and inv[0][1] and inv[0][1] != self.name:
frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}.
frappe.throw(
_(
"""Invoice {0} is tagged in another C-form: {1}.
If you want to change C-form no for this invoice,
please remove invoice no from the previous c-form and then try again"""\
.format(d.invoice_no, inv[0][1])))
please remove invoice no from the previous c-form and then try again""".format(
d.invoice_no, inv[0][1]
)
)
)
elif not inv:
frappe.throw(_("Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
Please enter a valid Invoice".format(d.idx, d.invoice_no)))
frappe.throw(
_(
"Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
Please enter a valid Invoice".format(
d.idx, d.invoice_no
)
)
)
def on_update(self):
""" Update C-Form No on invoices"""
"""Update C-Form No on invoices"""
self.set_total_invoiced_amount()
def on_submit(self):
@@ -43,30 +57,40 @@ class CForm(Document):
frappe.db.sql("""update `tabSales Invoice` set c_form_no=null where c_form_no=%s""", self.name)
def set_cform_in_sales_invoices(self):
inv = [d.invoice_no for d in self.get('invoices')]
inv = [d.invoice_no for d in self.get("invoices")]
if inv:
frappe.db.sql("""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)""" %
('%s', '%s', ', '.join(['%s'] * len(inv))), tuple([self.name, self.modified] + inv))
frappe.db.sql(
"""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)"""
% ("%s", "%s", ", ".join(["%s"] * len(inv))),
tuple([self.name, self.modified] + inv),
)
frappe.db.sql("""update `tabSales Invoice` set c_form_no = null, modified = %s
where name not in (%s) and ifnull(c_form_no, '') = %s""" %
('%s', ', '.join(['%s']*len(inv)), '%s'), tuple([self.modified] + inv + [self.name]))
frappe.db.sql(
"""update `tabSales Invoice` set c_form_no = null, modified = %s
where name not in (%s) and ifnull(c_form_no, '') = %s"""
% ("%s", ", ".join(["%s"] * len(inv)), "%s"),
tuple([self.modified] + inv + [self.name]),
)
else:
frappe.throw(_("Please enter atleast 1 invoice in the table"))
def set_total_invoiced_amount(self):
total = sum(flt(d.grand_total) for d in self.get('invoices'))
frappe.db.set(self, 'total_invoiced_amount', total)
total = sum(flt(d.grand_total) for d in self.get("invoices"))
frappe.db.set(self, "total_invoiced_amount", total)
@frappe.whitelist()
def get_invoice_details(self, invoice_no):
""" Pull details from invoices for referrence """
"""Pull details from invoices for referrence"""
if invoice_no:
inv = frappe.db.get_value("Sales Invoice", invoice_no,
["posting_date", "territory", "base_net_total", "base_grand_total"], as_dict=True)
inv = frappe.db.get_value(
"Sales Invoice",
invoice_no,
["posting_date", "territory", "base_net_total", "base_grand_total"],
as_dict=True,
)
return {
'invoice_date' : inv.posting_date,
'territory' : inv.territory,
'net_total' : inv.base_net_total,
'grand_total' : inv.base_grand_total
"invoice_date": inv.posting_date,
"territory": inv.territory,
"net_total": inv.base_net_total,
"grand_total": inv.base_grand_total,
}

View File

@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('C-Form')
class TestCForm(unittest.TestCase):
pass

View File

@@ -1,26 +1,25 @@
DEFAULT_MAPPERS = [
{
'doctype': 'Cash Flow Mapper',
'section_footer': 'Net cash generated by operating activities',
'section_header': 'Cash flows from operating activities',
'section_leader': 'Adjustments for',
'section_name': 'Operating Activities',
'position': 0,
'section_subtotal': 'Cash generated from operations',
},
{
'doctype': 'Cash Flow Mapper',
'position': 1,
'section_footer': 'Net cash used in investing activities',
'section_header': 'Cash flows from investing activities',
'section_name': 'Investing Activities'
},
{
'doctype': 'Cash Flow Mapper',
'position': 2,
'section_footer': 'Net cash used in financing activites',
'section_header': 'Cash flows from financing activities',
'section_name': 'Financing Activities',
}
{
"doctype": "Cash Flow Mapper",
"section_footer": "Net cash generated by operating activities",
"section_header": "Cash flows from operating activities",
"section_leader": "Adjustments for",
"section_name": "Operating Activities",
"position": 0,
"section_subtotal": "Cash generated from operations",
},
{
"doctype": "Cash Flow Mapper",
"position": 1,
"section_footer": "Net cash used in investing activities",
"section_header": "Cash flows from investing activities",
"section_name": "Investing Activities",
},
{
"doctype": "Cash Flow Mapper",
"position": 2,
"section_footer": "Net cash used in financing activites",
"section_header": "Cash flows from financing activities",
"section_name": "Financing Activities",
},
]

View File

@@ -11,9 +11,11 @@ class CashFlowMapping(Document):
self.validate_checked_options()
def validate_checked_options(self):
checked_fields = [d for d in self.meta.fields if d.fieldtype == 'Check' and self.get(d.fieldname) == 1]
checked_fields = [
d for d in self.meta.fields if d.fieldtype == "Check" and self.get(d.fieldname) == 1
]
if len(checked_fields) > 1:
frappe.throw(
frappe._('You can only select a maximum of one option from the list of check boxes.'),
title='Error'
frappe._("You can only select a maximum of one option from the list of check boxes."),
title="Error",
)

View File

@@ -9,19 +9,16 @@ import frappe
class TestCashFlowMapping(unittest.TestCase):
def setUp(self):
if frappe.db.exists("Cash Flow Mapping", "Test Mapping"):
frappe.delete_doc('Cash Flow Mappping', 'Test Mapping')
frappe.delete_doc("Cash Flow Mappping", "Test Mapping")
def tearDown(self):
frappe.delete_doc('Cash Flow Mapping', 'Test Mapping')
frappe.delete_doc("Cash Flow Mapping", "Test Mapping")
def test_multiple_selections_not_allowed(self):
doc = frappe.new_doc('Cash Flow Mapping')
doc.mapping_name = 'Test Mapping'
doc.label = 'Test label'
doc.append(
'accounts',
{'account': 'Accounts Receivable - _TC'}
)
doc = frappe.new_doc("Cash Flow Mapping")
doc.mapping_name = "Test Mapping"
doc.label = "Test label"
doc.append("accounts", {"account": "Accounts Receivable - _TC"})
doc.is_working_capital = 1
doc.is_finance_cost = 1

View File

@@ -17,11 +17,14 @@ class CashierClosing(Document):
self.make_calculations()
def get_outstanding(self):
values = frappe.db.sql("""
values = frappe.db.sql(
"""
select sum(outstanding_amount)
from `tabSales Invoice`
where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
""", (self.date, self.from_time, self.time, self.user))
""",
(self.date, self.from_time, self.time, self.user),
)
self.outstanding_amount = flt(values[0][0] if values else 0)
def make_calculations(self):
@@ -29,7 +32,9 @@ class CashierClosing(Document):
for i in self.payments:
total += flt(i.amount)
self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
self.net_amount = (
total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
)
def validate_time(self):
if self.from_time >= self.time:

View File

@@ -25,33 +25,41 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document):
def validate(self):
if self.import_file:
get_coa('Chart of Accounts Importer', 'All Accounts', file_name=self.import_file, for_validate=1)
get_coa(
"Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
)
def validate_columns(data):
if not data:
frappe.throw(_('No data found. Seems like you uploaded a blank file'))
frappe.throw(_("No data found. Seems like you uploaded a blank file"))
no_of_columns = max([len(d) for d in data])
if no_of_columns > 7:
frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'),
title=(_("Wrong Template")))
frappe.throw(
_("More columns found than expected. Please compare the uploaded file with standard template"),
title=(_("Wrong Template")),
)
@frappe.whitelist()
def validate_company(company):
parent_company, allow_account_creation_against_child_company = frappe.db.get_value('Company',
{'name': company}, ['parent_company',
'allow_account_creation_against_child_company'])
parent_company, allow_account_creation_against_child_company = frappe.db.get_value(
"Company", {"name": company}, ["parent_company", "allow_account_creation_against_child_company"]
)
if parent_company and (not allow_account_creation_against_child_company):
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold('Allow Account Creation Against Child Company'))
frappe.throw(msg, title=_('Wrong Company'))
frappe.bold("Allow Account Creation Against Child Company")
)
frappe.throw(msg, title=_("Wrong Company"))
if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
if frappe.db.get_all("GL Entry", {"company": company}, "name", limit=1):
return False
@frappe.whitelist()
def import_coa(file_name, company):
# delete existing data for accounts
@@ -60,7 +68,7 @@ def import_coa(file_name, company):
# create accounts
file_doc, extension = get_file(file_name)
if extension == 'csv':
if extension == "csv":
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
@@ -72,27 +80,33 @@ def import_coa(file_name, company):
# trigger on_update for company to reset default accounts
set_default_accounts(company)
def get_file(file_name):
file_doc = frappe.get_doc("File", {"file_url": file_name})
parts = file_doc.get_extension()
extension = parts[1]
extension = extension.lstrip(".")
if extension not in ('csv', 'xlsx', 'xls'):
frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
if extension not in ("csv", "xlsx", "xls"):
frappe.throw(
_(
"Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"
)
)
return file_doc, extension
return file_doc, extension
def generate_data_from_csv(file_doc, as_dict=False):
''' read csv file and return the generated nested tree '''
"""read csv file and return the generated nested tree"""
file_path = file_doc.get_full_path()
data = []
with open(file_path, 'r') as in_file:
with open(file_path, "r") as in_file:
csv_reader = list(csv.reader(in_file))
headers = csv_reader[0]
del csv_reader[0] # delete top row and headers row
del csv_reader[0] # delete top row and headers row
for row in csv_reader:
if as_dict:
@@ -106,6 +120,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
# convert csv data
return data
def generate_data_from_excel(file_doc, extension, as_dict=False):
content = file_doc.get_content()
@@ -123,20 +138,21 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
if not row[1]:
row[1] = row[0]
row[3] = row[2]
row[1] = row[0]
row[3] = row[2]
data.append(row)
return data
@frappe.whitelist()
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
''' called by tree view (to fetch node's children) '''
"""called by tree view (to fetch node's children)"""
file_doc, extension = get_file(file_name)
parent = None if parent==_('All Accounts') else parent
parent = None if parent == _("All Accounts") else parent
if extension == 'csv':
if extension == "csv":
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
@@ -146,32 +162,33 @@ def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
if not for_validate:
forest = build_forest(data)
accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form
accounts = build_tree_from_json(
"", chart_data=forest, from_coa_importer=True
) # returns a list of dict in a tree render-able form
# filter out to show data for the selected node only
accounts = [d for d in accounts if d['parent_account']==parent]
accounts = [d for d in accounts if d["parent_account"] == parent]
return accounts
else:
return {
'show_import_button': 1
}
return {"show_import_button": 1}
def build_forest(data):
'''
converts list of list into a nested tree
if a = [[1,1], [1,2], [3,2], [4,4], [5,4]]
tree = {
1: {
2: {
3: {}
}
},
4: {
5: {}
}
}
'''
"""
converts list of list into a nested tree
if a = [[1,1], [1,2], [3,2], [4,4], [5,4]]
tree = {
1: {
2: {
3: {}
}
},
4: {
5: {}
}
}
"""
# set the value of nested dictionary
def set_nested(d, path, value):
@@ -195,8 +212,11 @@ def build_forest(data):
elif account_name == child:
parent_account_list = return_parent(data, parent_account)
if not parent_account_list and parent_account:
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
frappe.bold(parent_account)))
frappe.throw(
_("The parent account {0} does not exists in the uploaded template").format(
frappe.bold(parent_account)
)
)
return [child] + parent_account_list
charts_map, paths = {}, []
@@ -205,7 +225,15 @@ def build_forest(data):
error_messages = []
for i in data:
account_name, parent_account, account_number, parent_account_number, is_group, account_type, root_type = i
(
account_name,
parent_account,
account_number,
parent_account_number,
is_group,
account_type,
root_type,
) = i
if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
@@ -216,13 +244,17 @@ def build_forest(data):
account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {}
charts_map[account_name]['account_name'] = name
if account_number: charts_map[account_name]["account_number"] = account_number
if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
if account_type: charts_map[account_name]["account_type"] = account_type
if root_type: charts_map[account_name]["root_type"] = root_type
charts_map[account_name]["account_name"] = name
if account_number:
charts_map[account_name]["account_number"] = account_number
if cint(is_group) == 1:
charts_map[account_name]["is_group"] = is_group
if account_type:
charts_map[account_name]["account_type"] = account_type
if root_type:
charts_map[account_name]["root_type"] = root_type
path = return_parent(data, account_name)[::-1]
paths.append(path) # List of path is created
paths.append(path) # List of path is created
line_no += 1
if error_messages:
@@ -231,27 +263,32 @@ def build_forest(data):
out = {}
for path in paths:
for n, account_name in enumerate(path):
set_nested(out, path[:n+1], charts_map[account_name]) # setting the value of nested dictionary.
set_nested(
out, path[: n + 1], charts_map[account_name]
) # setting the value of nested dictionary.
return out
def build_response_as_excel(writer):
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(writer.getvalue()).encode('utf-8'))
with open(filename, "wb") as f:
f.write(cstr(writer.getvalue()).encode("utf-8"))
f = open(filename)
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
f.close()
os.remove(filename)
# write out response as a xlsx type
frappe.response['filename'] = 'coa_importer_template.xlsx'
frappe.response['filecontent'] = xlsx_file.getvalue()
frappe.response['type'] = 'binary'
frappe.response["filename"] = "coa_importer_template.xlsx"
frappe.response["filecontent"] = xlsx_file.getvalue()
frappe.response["type"] = "binary"
@frappe.whitelist()
def download_template(file_type, template_type):
@@ -259,34 +296,46 @@ def download_template(file_type, template_type):
writer = get_template(template_type)
if file_type == 'CSV':
if file_type == "CSV":
# download csv file
frappe.response['result'] = cstr(writer.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = 'Chart of Accounts Importer'
frappe.response["result"] = cstr(writer.getvalue())
frappe.response["type"] = "csv"
frappe.response["doctype"] = "Chart of Accounts Importer"
else:
build_response_as_excel(writer)
def get_template(template_type):
fields = ["Account Name", "Parent Account", "Account Number", "Parent Account Number", "Is Group", "Account Type", "Root Type"]
fields = [
"Account Name",
"Parent Account",
"Account Number",
"Parent Account Number",
"Is Group",
"Account Type",
"Root Type",
]
writer = UnicodeWriter()
writer.writerow(fields)
if template_type == 'Blank Template':
for root_type in get_root_types():
writer.writerow(['', '', '', 1, '', root_type])
if template_type == "Blank Template":
for root_type in get_root_types():
writer.writerow(["", "", "", 1, "", root_type])
for account in get_mandatory_group_accounts():
writer.writerow(['', '', '', 1, account, "Asset"])
writer.writerow(["", "", "", 1, account, "Asset"])
for account_type in get_mandatory_account_types():
writer.writerow(['', '', '', 0, account_type.get('account_type'), account_type.get('root_type')])
writer.writerow(
["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
)
else:
writer = get_sample_template(writer)
return writer
def get_sample_template(writer):
template = [
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
@@ -316,7 +365,7 @@ def get_sample_template(writer):
@frappe.whitelist()
def validate_accounts(file_doc, extension):
if extension == 'csv':
if extension == "csv":
accounts = generate_data_from_csv(file_doc, as_dict=True)
else:
accounts = generate_data_from_excel(file_doc, extension, as_dict=True)
@@ -325,7 +374,9 @@ def validate_accounts(file_doc, extension):
for account in accounts:
accounts_dict.setdefault(account["account_name"], account)
if "parent_account" not in account:
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
msg = _(
"Please make sure the file you are using has 'Parent Account' column present in the header."
)
msg += "<br><br>"
msg += _("Alternatively, you can download the template and fill your data in.")
frappe.throw(msg, title=_("Parent Account Missing"))
@@ -336,77 +387,106 @@ def validate_accounts(file_doc, extension):
return [True, len(accounts)]
def validate_root(accounts):
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
roots = [accounts[d] for d in accounts if not accounts[d].get("parent_account")]
error_messages = []
for account in roots:
if not account.get("root_type") and account.get("account_name"):
error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name")))
error_messages.append(
_("Please enter Root Type for account- {0}").format(account.get("account_name"))
)
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
error_messages.append(
_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(
account.get("account_name")
)
)
validate_missing_roots(roots)
if error_messages:
frappe.throw("<br>".join(error_messages))
def validate_missing_roots(roots):
root_types_added = set(d.get('root_type') for d in roots)
root_types_added = set(d.get("root_type") for d in roots)
missing = list(set(get_root_types()) - root_types_added)
if missing:
frappe.throw(_("Please add Root Account for - {0}").format(' , '.join(missing)))
frappe.throw(_("Please add Root Account for - {0}").format(" , ".join(missing)))
def get_root_types():
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
return ("Asset", "Liability", "Expense", "Income", "Equity")
def get_report_type(root_type):
if root_type in ('Asset', 'Liability', 'Equity'):
return 'Balance Sheet'
if root_type in ("Asset", "Liability", "Equity"):
return "Balance Sheet"
else:
return 'Profit and Loss'
return "Profit and Loss"
def get_mandatory_group_accounts():
return ('Bank', 'Cash', 'Stock')
return ("Bank", "Cash", "Stock")
def get_mandatory_account_types():
return [
{'account_type': 'Cost of Goods Sold', 'root_type': 'Expense'},
{'account_type': 'Depreciation', 'root_type': 'Expense'},
{'account_type': 'Fixed Asset', 'root_type': 'Asset'},
{'account_type': 'Payable', 'root_type': 'Liability'},
{'account_type': 'Receivable', 'root_type': 'Asset'},
{'account_type': 'Stock Adjustment', 'root_type': 'Expense'},
{'account_type': 'Bank', 'root_type': 'Asset'},
{'account_type': 'Cash', 'root_type': 'Asset'},
{'account_type': 'Stock', 'root_type': 'Asset'}
{"account_type": "Cost of Goods Sold", "root_type": "Expense"},
{"account_type": "Depreciation", "root_type": "Expense"},
{"account_type": "Fixed Asset", "root_type": "Asset"},
{"account_type": "Payable", "root_type": "Liability"},
{"account_type": "Receivable", "root_type": "Asset"},
{"account_type": "Stock Adjustment", "root_type": "Expense"},
{"account_type": "Bank", "root_type": "Asset"},
{"account_type": "Cash", "root_type": "Asset"},
{"account_type": "Stock", "root_type": "Asset"},
]
def unset_existing_data(company):
linked = frappe.db.sql('''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)
linked = frappe.db.sql(
'''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''',
as_dict=True,
)
# remove accounts data from company
update_values = {d.fieldname: '' for d in linked}
frappe.db.set_value('Company', company, update_values, update_values)
update_values = {d.fieldname: "" for d in linked}
frappe.db.set_value("Company", company, update_values, update_values)
# remove accounts data from various doctypes
for doctype in ["Account", "Party Account", "Mode of Payment Account", "Tax Withholding Account",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
frappe.db.sql('''delete from `tab{0}` where `company`="%s"''' # nosec
.format(doctype) % (company))
for doctype in [
"Account",
"Party Account",
"Mode of Payment Account",
"Tax Withholding Account",
"Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template",
]:
frappe.db.sql(
'''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
)
def set_default_accounts(company):
from erpnext.setup.doctype.company.company import install_country_fixtures
company = frappe.get_doc('Company', company)
company.update({
"default_receivable_account": frappe.db.get_value("Account",
{"company": company.name, "account_type": "Receivable", "is_group": 0}),
"default_payable_account": frappe.db.get_value("Account",
{"company": company.name, "account_type": "Payable", "is_group": 0})
})
company = frappe.get_doc("Company", company)
company.update(
{
"default_receivable_account": frappe.db.get_value(
"Account", {"company": company.name, "account_type": "Receivable", "is_group": 0}
),
"default_payable_account": frappe.db.get_value(
"Account", {"company": company.name, "account_type": "Payable", "is_group": 0}
),
}
)
company.save()
install_country_fixtures(company.name, company.country)

View File

@@ -10,17 +10,20 @@ from frappe.model.document import Document
class ChequePrintTemplate(Document):
pass
@frappe.whitelist()
def create_or_update_cheque_print_format(template_name):
if not frappe.db.exists("Print Format", template_name):
cheque_print = frappe.new_doc("Print Format")
cheque_print.update({
"doc_type": "Payment Entry",
"standard": "No",
"custom_format": 1,
"print_format_type": "Jinja",
"name": template_name
})
cheque_print.update(
{
"doc_type": "Payment Entry",
"standard": "No",
"custom_format": 1,
"print_format_type": "Jinja",
"name": template_name,
}
)
else:
cheque_print = frappe.get_doc("Print Format", template_name)
@@ -69,10 +72,12 @@ def create_or_update_cheque_print_format(template_name):
{{doc.company}}
</span>
</div>
</div>"""%{
"starting_position_from_top_edge": doc.starting_position_from_top_edge \
if doc.cheque_size == "A4" else 0.0,
"cheque_width": doc.cheque_width, "cheque_height": doc.cheque_height,
</div>""" % {
"starting_position_from_top_edge": doc.starting_position_from_top_edge
if doc.cheque_size == "A4"
else 0.0,
"cheque_width": doc.cheque_width,
"cheque_height": doc.cheque_height,
"acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
"acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
"message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
@@ -89,7 +94,7 @@ def create_or_update_cheque_print_format(template_name):
"amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
"amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
"signatory_from_top_edge": doc.signatory_from_top_edge,
"signatory_from_left_edge": doc.signatory_from_left_edge
"signatory_from_left_edge": doc.signatory_from_left_edge,
}
cheque_print.save(ignore_permissions=True)

View File

@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Cheque Print Template')
class TestChequePrintTemplate(unittest.TestCase):
pass

View File

@@ -11,11 +11,14 @@ from erpnext.accounts.utils import validate_field_number
class CostCenter(NestedSet):
nsm_parent_field = 'parent_cost_center'
nsm_parent_field = "parent_cost_center"
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, None, self.company)
self.name = get_autoname_with_number(
self.cost_center_number, self.cost_center_name, None, self.company
)
def validate(self):
self.validate_mandatory()
@@ -27,15 +30,31 @@ class CostCenter(NestedSet):
if not self.distributed_cost_center:
frappe.throw(_("Please enter distributed cost center"))
if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
if not self.get('__islocal'):
if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
and self.check_if_part_of_distributed_cost_center():
frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
frappe.throw(
_("Total percentage allocation for distributed cost center should be equal to 100")
)
if not self.get("__islocal"):
if (
not cint(
frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")
)
and self.check_if_part_of_distributed_cost_center()
):
frappe.throw(
_(
"Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"
)
)
if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
if check_if_distributed_cost_center_enabled(
list(x.cost_center for x in self.distributed_cost_center)
):
frappe.throw(
_(
"A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."
)
)
else:
self.distributed_cost_center = []
@@ -47,9 +66,12 @@ class CostCenter(NestedSet):
def validate_parent_cost_center(self):
if self.parent_cost_center:
if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'):
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
frappe.bold(self.parent_cost_center)))
if not frappe.db.get_value("Cost Center", self.parent_cost_center, "is_group"):
frappe.throw(
_("{0} is not a group node. Please select a group node as parent cost center").format(
frappe.bold(self.parent_cost_center)
)
)
@frappe.whitelist()
def convert_group_to_ledger(self):
@@ -65,9 +87,13 @@ class CostCenter(NestedSet):
@frappe.whitelist()
def convert_ledger_to_group(self):
if cint(self.enable_distributed_cost_center):
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
frappe.throw(
_("Cost Center with enabled distributed cost center can not be converted to group")
)
if self.check_if_part_of_distributed_cost_center():
frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
frappe.throw(
_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group")
)
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
self.is_group = 1
@@ -78,8 +104,11 @@ class CostCenter(NestedSet):
return frappe.db.get_value("GL Entry", {"cost_center": self.name})
def check_if_child_exists(self):
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
return frappe.db.sql(
"select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2",
self.name,
)
def check_if_part_of_distributed_cost_center(self):
return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
@@ -87,6 +116,7 @@ class CostCenter(NestedSet):
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
new_cost_center = get_name_with_abbr(newdn, self.company)
# Validate properties before merging
@@ -100,7 +130,9 @@ class CostCenter(NestedSet):
super(CostCenter, self).after_rename(olddn, newdn, merge)
if not merge:
new_cost_center = frappe.db.get_value("Cost Center", newdn, ["cost_center_name", "cost_center_number"], as_dict=1)
new_cost_center = frappe.db.get_value(
"Cost Center", newdn, ["cost_center_name", "cost_center_number"], as_dict=1
)
# exclude company abbr
new_parts = newdn.split(" - ")[:-1]
@@ -109,7 +141,9 @@ class CostCenter(NestedSet):
if len(new_parts) == 1:
new_parts = newdn.split(" ")
if new_cost_center.cost_center_number != new_parts[0]:
validate_field_number("Cost Center", self.name, new_parts[0], self.company, "cost_center_number")
validate_field_number(
"Cost Center", self.name, new_parts[0], self.company, "cost_center_number"
)
self.cost_center_number = new_parts[0]
self.db_set("cost_center_number", new_parts[0])
new_parts = new_parts[1:]
@@ -120,14 +154,19 @@ class CostCenter(NestedSet):
self.cost_center_name = cost_center_name
self.db_set("cost_center_name", cost_center_name)
def on_doctype_update():
frappe.db.add_index("Cost Center", ["lft", "rgt"])
def get_name_with_number(new_account, account_number):
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
return new_account
def check_if_distributed_cost_center_enabled(cost_center_list):
value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
value_list = frappe.get_list(
"Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1
)
return next((True for x in value_list if x[0]), False)

View File

@@ -1,14 +1,8 @@
from frappe import _
def get_data():
return {
'fieldname': 'cost_center',
'reports': [
{
'label': _('Reports'),
'items': ['Budget Variance Report', 'General Ledger']
}
]
"fieldname": "cost_center",
"reports": [{"label": _("Reports"), "items": ["Budget Variance Report", "General Ledger"]}],
}

View File

@@ -5,51 +5,53 @@ import unittest
import frappe
test_records = frappe.get_test_records('Cost Center')
test_records = frappe.get_test_records("Cost Center")
class TestCostCenter(unittest.TestCase):
def test_cost_center_creation_against_child_node(self):
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert()
cost_center = frappe.get_doc({
'doctype': 'Cost Center',
'cost_center_name': '_Test Cost Center 3',
'parent_cost_center': '_Test Cost Center 2 - _TC',
'is_group': 0,
'company': '_Test Company'
})
cost_center = frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "_Test Cost Center 3",
"parent_cost_center": "_Test Cost Center 2 - _TC",
"is_group": 0,
"company": "_Test Company",
}
)
self.assertRaises(frappe.ValidationError, cost_center.save)
def test_validate_distributed_cost_center(self):
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center - _TC"}):
frappe.get_doc(test_records[0]).insert()
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert()
invalid_distributed_cost_center = frappe.get_doc({
"company": "_Test Company",
"cost_center_name": "_Test Distributed Cost Center",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC",
"enable_distributed_cost_center": 1,
"distributed_cost_center": [{
"cost_center": "_Test Cost Center - _TC",
"percentage_allocation": 40
}, {
"cost_center": "_Test Cost Center 2 - _TC",
"percentage_allocation": 50
}
]
})
invalid_distributed_cost_center = frappe.get_doc(
{
"company": "_Test Company",
"cost_center_name": "_Test Distributed Cost Center",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC",
"enable_distributed_cost_center": 1,
"distributed_cost_center": [
{"cost_center": "_Test Cost Center - _TC", "percentage_allocation": 40},
{"cost_center": "_Test Cost Center 2 - _TC", "percentage_allocation": 50},
],
}
)
self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:

View File

@@ -15,7 +15,7 @@ class CouponCode(Document):
if not self.coupon_code:
if self.coupon_type == "Promotional":
self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
self.coupon_code = "".join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
elif self.coupon_type == "Gift Card":
self.coupon_code = frappe.generate_hash()[:10].upper()

View File

@@ -7,92 +7,110 @@ import frappe
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
test_dependencies = ['Item']
test_dependencies = ["Item"]
def test_create_test_data():
frappe.set_user("Administrator")
# create test item
if not frappe.db.exists("Item","_Test Tesla Car"):
item = frappe.get_doc({
"description": "_Test Tesla Car",
"doctype": "Item",
"has_batch_no": 0,
"has_serial_no": 0,
"inspection_required": 0,
"is_stock_item": 1,
"opening_stock":100,
"is_sub_contracted_item": 0,
"item_code": "_Test Tesla Car",
"item_group": "_Test Item Group",
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
"warehouse":"Stores - _TC",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate":5000,
"item_defaults": [{
"company": "_Test Company",
"default_warehouse": "Stores - _TC",
"default_price_list":"_Test Price List",
"expense_account": "Cost of Goods Sold - _TC",
"buying_cost_center": "Main - _TC",
"selling_cost_center": "Main - _TC",
"income_account": "Sales - _TC"
}],
})
if not frappe.db.exists("Item", "_Test Tesla Car"):
item = frappe.get_doc(
{
"description": "_Test Tesla Car",
"doctype": "Item",
"has_batch_no": 0,
"has_serial_no": 0,
"inspection_required": 0,
"is_stock_item": 1,
"opening_stock": 100,
"is_sub_contracted_item": 0,
"item_code": "_Test Tesla Car",
"item_group": "_Test Item Group",
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
"warehouse": "Stores - _TC",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate": 5000,
"item_defaults": [
{
"company": "_Test Company",
"default_warehouse": "Stores - _TC",
"default_price_list": "_Test Price List",
"expense_account": "Cost of Goods Sold - _TC",
"buying_cost_center": "Main - _TC",
"selling_cost_center": "Main - _TC",
"income_account": "Sales - _TC",
}
],
}
)
item.insert()
# create test item price
item_price = frappe.get_list('Item Price', filters={'item_code': '_Test Tesla Car', 'price_list': '_Test Price List'}, fields=['name'])
if len(item_price)==0:
item_price = frappe.get_doc({
"doctype": "Item Price",
"item_code": "_Test Tesla Car",
"price_list": "_Test Price List",
"price_list_rate": 5000
})
item_price = frappe.get_list(
"Item Price",
filters={"item_code": "_Test Tesla Car", "price_list": "_Test Price List"},
fields=["name"],
)
if len(item_price) == 0:
item_price = frappe.get_doc(
{
"doctype": "Item Price",
"item_code": "_Test Tesla Car",
"price_list": "_Test Price List",
"price_list_rate": 5000,
}
)
item_price.insert()
# create test item pricing rule
if not frappe.db.exists("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}):
item_pricing_rule = frappe.get_doc({
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule for _Test Item",
"apply_on": "Item Code",
"items": [{
"item_code": "_Test Tesla Car"
}],
"warehouse":"Stores - _TC",
"coupon_code_based":1,
"selling": 1,
"rate_or_discount": "Discount Percentage",
"discount_percentage": 30,
"company": "_Test Company",
"currency":"INR",
"for_price_list":"_Test Price List"
})
item_pricing_rule = frappe.get_doc(
{
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule for _Test Item",
"apply_on": "Item Code",
"items": [{"item_code": "_Test Tesla Car"}],
"warehouse": "Stores - _TC",
"coupon_code_based": 1,
"selling": 1,
"rate_or_discount": "Discount Percentage",
"discount_percentage": 30,
"company": "_Test Company",
"currency": "INR",
"for_price_list": "_Test Price List",
}
)
item_pricing_rule.insert()
# create test item sales partner
if not frappe.db.exists("Sales Partner","_Test Coupon Partner"):
sales_partner = frappe.get_doc({
"doctype": "Sales Partner",
"partner_name":"_Test Coupon Partner",
"commission_rate":2,
"referral_code": "COPART"
})
if not frappe.db.exists("Sales Partner", "_Test Coupon Partner"):
sales_partner = frappe.get_doc(
{
"doctype": "Sales Partner",
"partner_name": "_Test Coupon Partner",
"commission_rate": 2,
"referral_code": "COPART",
}
)
sales_partner.insert()
# create test item coupon code
if not frappe.db.exists("Coupon Code", "SAVE30"):
pricing_rule = frappe.db.get_value("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ['name'])
coupon_code = frappe.get_doc({
"doctype": "Coupon Code",
"coupon_name":"SAVE30",
"coupon_code":"SAVE30",
"pricing_rule": pricing_rule,
"valid_from": "2014-01-01",
"maximum_use":1,
"used":0
})
pricing_rule = frappe.db.get_value(
"Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ["name"]
)
coupon_code = frappe.get_doc(
{
"doctype": "Coupon Code",
"coupon_name": "SAVE30",
"coupon_code": "SAVE30",
"pricing_rule": pricing_rule,
"valid_from": "2014-01-01",
"maximum_use": 1,
"used": 0,
}
)
coupon_code.insert()
class TestCouponCode(unittest.TestCase):
def setUp(self):
test_create_test_data()
@@ -103,15 +121,21 @@ class TestCouponCode(unittest.TestCase):
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
customer="_Test Customer", selling_price_list="_Test Price List",
item_code="_Test Tesla Car", rate=5000, qty=1,
do_not_submit=True)
so = make_sales_order(
company="_Test Company",
warehouse="Stores - _TC",
customer="_Test Customer",
selling_price_list="_Test Price List",
item_code="_Test Tesla Car",
rate=5000,
qty=1,
do_not_submit=True,
)
self.assertEqual(so.items[0].rate, 5000)
so.coupon_code='SAVE30'
so.sales_partner='_Test Coupon Partner'
so.coupon_code = "SAVE30"
so.sales_partner = "_Test Coupon Partner"
so.save()
# check item price after coupon code is applied

View File

@@ -20,78 +20,99 @@ class Dunning(AccountsController):
self.validate_overdue_days()
self.validate_amount()
if not self.income_account:
self.income_account = frappe.db.get_value('Company', self.company, 'default_income_account')
self.income_account = frappe.db.get_value("Company", self.company, "default_income_account")
def validate_overdue_days(self):
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
def validate_amount(self):
amounts = calculate_interest_and_amount(
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
if self.interest_amount != amounts.get('interest_amount'):
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
if self.dunning_amount != amounts.get('dunning_amount'):
self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount'))
if self.grand_total != amounts.get('grand_total'):
self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total'))
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
)
if self.interest_amount != amounts.get("interest_amount"):
self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
if self.dunning_amount != amounts.get("dunning_amount"):
self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
if self.grand_total != amounts.get("grand_total"):
self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
def on_submit(self):
self.make_gl_entries()
def on_cancel(self):
if self.dunning_amount:
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def make_gl_entries(self):
if not self.dunning_amount:
return
gl_entries = []
invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"]
invoice_fields = [
"project",
"cost_center",
"debit_to",
"party_account_currency",
"conversion_rate",
"cost_center",
]
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
accounting_dimensions = get_accounting_dimensions()
invoice_fields.extend(accounting_dimensions)
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
gl_entries.append(
self.get_gl_dict({
"account": inv.debit_to,
"party_type": "Customer",
"party": self.customer,
"due_date": self.due_date,
"against": self.income_account,
"debit": dunning_in_company_currency,
"debit_in_account_currency": self.dunning_amount,
"against_voucher": self.name,
"against_voucher_type": "Dunning",
"cost_center": inv.cost_center or default_cost_center,
"project": inv.project
}, inv.party_account_currency, item=inv)
self.get_gl_dict(
{
"account": inv.debit_to,
"party_type": "Customer",
"party": self.customer,
"due_date": self.due_date,
"against": self.income_account,
"debit": dunning_in_company_currency,
"debit_in_account_currency": self.dunning_amount,
"against_voucher": self.name,
"against_voucher_type": "Dunning",
"cost_center": inv.cost_center or default_cost_center,
"project": inv.project,
},
inv.party_account_currency,
item=inv,
)
)
gl_entries.append(
self.get_gl_dict({
"account": self.income_account,
"against": self.customer,
"credit": dunning_in_company_currency,
"cost_center": inv.cost_center or default_cost_center,
"credit_in_account_currency": self.dunning_amount,
"project": inv.project
}, item=inv)
self.get_gl_dict(
{
"account": self.income_account,
"against": self.customer,
"credit": dunning_in_company_currency,
"cost_center": inv.cost_center or default_cost_center,
"credit_in_account_currency": self.dunning_amount,
"project": inv.project,
},
item=inv,
)
)
make_gl_entries(
gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
)
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False)
def resolve_dunning(doc, state):
for reference in doc.references:
if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
dunnings = frappe.get_list('Dunning', filters={
'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True)
if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
dunnings = frappe.get_list(
"Dunning",
filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")},
ignore_permissions=True,
)
for dunning in dunnings:
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
interest_amount = 0
@@ -102,23 +123,26 @@ def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_
grand_total += flt(interest_amount)
dunning_amount = flt(interest_amount) + flt(dunning_fee)
return {
'interest_amount': interest_amount,
'grand_total': grand_total,
'dunning_amount': dunning_amount}
"interest_amount": interest_amount,
"grand_total": grand_total,
"dunning_amount": dunning_amount,
}
@frappe.whitelist()
def get_dunning_letter_text(dunning_type, doc, language=None):
if isinstance(doc, string_types):
doc = json.loads(doc)
if language:
filters = {'parent': dunning_type, 'language': language}
filters = {"parent": dunning_type, "language": language}
else:
filters = {'parent': dunning_type, 'is_default_language': 1}
letter_text = frappe.db.get_value('Dunning Letter Text', filters,
['body_text', 'closing_text', 'language'], as_dict=1)
filters = {"parent": dunning_type, "is_default_language": 1}
letter_text = frappe.db.get_value(
"Dunning Letter Text", filters, ["body_text", "closing_text", "language"], as_dict=1
)
if letter_text:
return {
'body_text': frappe.render_template(letter_text.body_text, doc),
'closing_text': frappe.render_template(letter_text.closing_text, doc),
'language': letter_text.language
"body_text": frappe.render_template(letter_text.body_text, doc),
"closing_text": frappe.render_template(letter_text.closing_text, doc),
"language": letter_text.language,
}

View File

@@ -1,18 +1,12 @@
from frappe import _
def get_data():
return {
'fieldname': 'dunning',
'non_standard_fieldnames': {
'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name'
"fieldname": "dunning",
"non_standard_fieldnames": {
"Journal Entry": "reference_name",
"Payment Entry": "reference_name",
},
'transactions': [
{
'label': _('Payment'),
'items': ['Payment Entry', 'Journal Entry']
}
]
"transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}],
}

View File

@@ -30,31 +30,35 @@ class TestDunning(unittest.TestCase):
def test_dunning(self):
dunning = create_dunning()
amounts = calculate_interest_and_amount(
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
)
self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44)
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44)
self.assertEqual(round(amounts.get("grand_total"), 2), 120.44)
def test_dunning_with_zero_interest_rate(self):
dunning = create_dunning_with_zero_interest_rate()
amounts = calculate_interest_and_amount(
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
self.assertEqual(round(amounts.get('interest_amount'), 2), 0)
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20)
self.assertEqual(round(amounts.get('grand_total'), 2), 120)
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
)
self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
self.assertEqual(round(amounts.get("grand_total"), 2), 120)
def test_gl_entries(self):
dunning = create_dunning()
dunning.submit()
gl_entries = frappe.db.sql("""select account, debit, credit
gl_entries = frappe.db.sql(
"""select account, debit, credit
from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
order by account asc""", dunning.name, as_dict=1)
order by account asc""",
dunning.name,
as_dict=1,
)
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
['Debtors - _TC', 20.44, 0.0],
['Sales - _TC', 0.0, 20.44]
])
expected_values = dict(
(d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]]
)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
@@ -72,7 +76,7 @@ class TestDunning(unittest.TestCase):
pe.target_exchange_rate = 1
pe.insert()
pe.submit()
si_doc = frappe.get_doc('Sales Invoice', dunning.sales_invoice)
si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
self.assertEqual(si_doc.outstanding_amount, 0)
@@ -80,8 +84,9 @@ def create_dunning():
posting_date = add_days(today(), -20)
due_date = add_days(today(), -15)
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=posting_date, due_date=due_date, status='Overdue')
dunning_type = frappe.get_doc("Dunning Type", 'First Notice')
posting_date=posting_date, due_date=due_date, status="Overdue"
)
dunning_type = frappe.get_doc("Dunning Type", "First Notice")
dunning = frappe.new_doc("Dunning")
dunning.sales_invoice = sales_invoice.name
dunning.customer_name = sales_invoice.customer_name
@@ -91,18 +96,20 @@ def create_dunning():
dunning.company = sales_invoice.company
dunning.posting_date = nowdate()
dunning.due_date = sales_invoice.due_date
dunning.dunning_type = 'First Notice'
dunning.dunning_type = "First Notice"
dunning.rate_of_interest = dunning_type.rate_of_interest
dunning.dunning_fee = dunning_type.dunning_fee
dunning.save()
return dunning
def create_dunning_with_zero_interest_rate():
posting_date = add_days(today(), -20)
due_date = add_days(today(), -15)
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=posting_date, due_date=due_date, status='Overdue')
dunning_type = frappe.get_doc("Dunning Type", 'First Notice with 0% Rate of Interest')
posting_date=posting_date, due_date=due_date, status="Overdue"
)
dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest")
dunning = frappe.new_doc("Dunning")
dunning.sales_invoice = sales_invoice.name
dunning.customer_name = sales_invoice.customer_name
@@ -112,40 +119,44 @@ def create_dunning_with_zero_interest_rate():
dunning.company = sales_invoice.company
dunning.posting_date = nowdate()
dunning.due_date = sales_invoice.due_date
dunning.dunning_type = 'First Notice with 0% Rate of Interest'
dunning.dunning_type = "First Notice with 0% Rate of Interest"
dunning.rate_of_interest = dunning_type.rate_of_interest
dunning.dunning_fee = dunning_type.dunning_fee
dunning.save()
return dunning
def create_dunning_type():
dunning_type = frappe.new_doc("Dunning Type")
dunning_type.dunning_type = 'First Notice'
dunning_type.dunning_type = "First Notice"
dunning_type.start_day = 10
dunning_type.end_day = 20
dunning_type.dunning_fee = 20
dunning_type.rate_of_interest = 8
dunning_type.append(
"dunning_letter_text", {
'language': 'en',
'body_text': 'We have still not received payment for our invoice ',
'closing_text': 'We kindly request that you pay the outstanding amount immediately, including interest and late fees.'
}
"dunning_letter_text",
{
"language": "en",
"body_text": "We have still not received payment for our invoice ",
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
},
)
dunning_type.save()
def create_dunning_type_with_zero_interest_rate():
dunning_type = frappe.new_doc("Dunning Type")
dunning_type.dunning_type = 'First Notice with 0% Rate of Interest'
dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
dunning_type.start_day = 10
dunning_type.end_day = 20
dunning_type.dunning_fee = 20
dunning_type.rate_of_interest = 0
dunning_type.append(
"dunning_letter_text", {
'language': 'en',
'body_text': 'We have still not received payment for our invoice ',
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
}
"dunning_letter_text",
{
"language": "en",
"body_text": "We have still not received payment for our invoice ",
"closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.",
},
)
dunning_type.save()

View File

@@ -20,8 +20,9 @@ class ExchangeRateRevaluation(Document):
def set_total_gain_loss(self):
total_gain_loss = 0
for d in self.accounts:
d.gain_loss = flt(d.new_balance_in_base_currency, d.precision("new_balance_in_base_currency")) \
- flt(d.balance_in_base_currency, d.precision("balance_in_base_currency"))
d.gain_loss = flt(
d.new_balance_in_base_currency, d.precision("new_balance_in_base_currency")
) - flt(d.balance_in_base_currency, d.precision("balance_in_base_currency"))
total_gain_loss += flt(d.gain_loss, d.precision("gain_loss"))
self.total_gain_loss = flt(total_gain_loss, self.precision("total_gain_loss"))
@@ -30,15 +31,15 @@ class ExchangeRateRevaluation(Document):
frappe.throw(_("Please select Company and Posting Date to getting entries"))
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
self.ignore_linked_doctypes = "GL Entry"
@frappe.whitelist()
def check_journal_entry_condition(self):
total_debit = frappe.db.get_value("Journal Entry Account", {
'reference_type': 'Exchange Rate Revaluation',
'reference_name': self.name,
'docstatus': 1
}, "sum(debit) as sum")
total_debit = frappe.db.get_value(
"Journal Entry Account",
{"reference_type": "Exchange Rate Revaluation", "reference_name": self.name, "docstatus": 1},
"sum(debit) as sum",
)
total_amt = 0
for d in self.accounts:
@@ -54,28 +55,33 @@ class ExchangeRateRevaluation(Document):
accounts = []
self.validate_mandatory()
company_currency = erpnext.get_company_currency(self.company)
precision = get_field_precision(frappe.get_meta("Exchange Rate Revaluation Account")
.get_field("new_balance_in_base_currency"), company_currency)
precision = get_field_precision(
frappe.get_meta("Exchange Rate Revaluation Account").get_field("new_balance_in_base_currency"),
company_currency,
)
account_details = self.get_accounts_from_gle()
for d in account_details:
current_exchange_rate = d.balance / d.balance_in_account_currency \
if d.balance_in_account_currency else 0
current_exchange_rate = (
d.balance / d.balance_in_account_currency if d.balance_in_account_currency else 0
)
new_exchange_rate = get_exchange_rate(d.account_currency, company_currency, self.posting_date)
new_balance_in_base_currency = flt(d.balance_in_account_currency * new_exchange_rate)
gain_loss = flt(new_balance_in_base_currency, precision) - flt(d.balance, precision)
if gain_loss:
accounts.append({
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency
})
accounts.append(
{
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
}
)
if not accounts:
self.throw_invalid_response_message(account_details)
@@ -84,7 +90,8 @@ class ExchangeRateRevaluation(Document):
def get_accounts_from_gle(self):
company_currency = erpnext.get_company_currency(self.company)
accounts = frappe.db.sql_list("""
accounts = frappe.db.sql_list(
"""
select name
from tabAccount
where is_group = 0
@@ -93,11 +100,14 @@ class ExchangeRateRevaluation(Document):
and account_type != 'Stock'
and company=%s
and account_currency != %s
order by name""",(self.company, company_currency))
order by name""",
(self.company, company_currency),
)
account_details = []
if accounts:
account_details = frappe.db.sql("""
account_details = frappe.db.sql(
"""
select
account, party_type, party, account_currency,
sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency,
@@ -109,7 +119,11 @@ class ExchangeRateRevaluation(Document):
group by account, NULLIF(party_type,''), NULLIF(party,'')
having sum(debit) != sum(credit)
order by account
""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
"""
% (", ".join(["%s"] * len(accounts)), "%s"),
tuple(accounts + [self.posting_date]),
as_dict=1,
)
return account_details
@@ -125,77 +139,107 @@ class ExchangeRateRevaluation(Document):
if self.total_gain_loss == 0:
return
unrealized_exchange_gain_loss_account = frappe.get_cached_value('Company', self.company,
"unrealized_exchange_gain_loss_account")
unrealized_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "unrealized_exchange_gain_loss_account"
)
if not unrealized_exchange_gain_loss_account:
frappe.throw(_("Please set Unrealized Exchange Gain/Loss Account in Company {0}")
.format(self.company))
frappe.throw(
_("Please set Unrealized Exchange Gain/Loss Account in Company {0}").format(self.company)
)
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Exchange Rate Revaluation'
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = "Exchange Rate Revaluation"
journal_entry.company = self.company
journal_entry.posting_date = self.posting_date
journal_entry.multi_currency = 1
journal_entry_accounts = []
for d in self.accounts:
dr_or_cr = "debit_in_account_currency" \
if d.get("balance_in_account_currency") > 0 else "credit_in_account_currency"
dr_or_cr = (
"debit_in_account_currency"
if d.get("balance_in_account_currency") > 0
else "credit_in_account_currency"
)
reverse_dr_or_cr = "debit_in_account_currency" \
if dr_or_cr=="credit_in_account_currency" else "credit_in_account_currency"
reverse_dr_or_cr = (
"debit_in_account_currency"
if dr_or_cr == "credit_in_account_currency"
else "credit_in_account_currency"
)
journal_entry_accounts.append({
"account": d.get("account"),
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
journal_entry_accounts.append(
{
"account": d.get("account"),
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
"balance": flt(
d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")
),
dr_or_cr: flt(
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
),
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry_accounts.append(
{
"account": d.get("account"),
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
"balance": flt(
d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")
),
reverse_dr_or_cr: flt(
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
),
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit_in_account_currency": abs(self.total_gain_loss) if self.total_gain_loss < 0 else 0,
"credit_in_account_currency": self.total_gain_loss if self.total_gain_loss > 0 else 0,
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
})
journal_entry_accounts.append({
"account": d.get("account"),
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name
})
journal_entry_accounts.append({
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit_in_account_currency": abs(self.total_gain_loss) if self.total_gain_loss < 0 else 0,
"credit_in_account_currency": self.total_gain_loss if self.total_gain_loss > 0 else 0,
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
})
}
)
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_amounts_in_company_currency()
journal_entry.set_total_debit_credit()
return journal_entry.as_dict()
@frappe.whitelist()
def get_account_details(account, company, posting_date, party_type=None, party=None):
account_currency, account_type = frappe.db.get_value("Account", account,
["account_currency", "account_type"])
account_currency, account_type = frappe.db.get_value(
"Account", account, ["account_currency", "account_type"]
)
if account_type in ["Receivable", "Payable"] and not (party_type and party):
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
account_details = {}
company_currency = erpnext.get_company_currency(company)
balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
balance = get_balance_on(
account, date=posting_date, party_type=party_type, party=party, in_account_currency=False
)
if balance:
balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
balance_in_account_currency = get_balance_on(
account, date=posting_date, party_type=party_type, party=party
)
current_exchange_rate = (
balance / balance_in_account_currency if balance_in_account_currency else 0
)
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
account_details = {
@@ -204,7 +248,7 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
"balance_in_account_currency": balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency
"new_balance_in_base_currency": new_balance_in_base_currency,
}
return account_details

View File

@@ -1,11 +1,2 @@
def get_data():
return {
'fieldname': 'reference_name',
'transactions': [
{
'items': ['Journal Entry']
}
]
}
return {"fieldname": "reference_name", "transactions": [{"items": ["Journal Entry"]}]}

View File

@@ -1,24 +1,13 @@
from frappe import _
def get_data():
return {
'fieldname': 'finance_book',
'non_standard_fieldnames': {
'Asset': 'default_finance_book',
'Company': 'default_finance_book'
},
'transactions': [
{
'label': _('Assets'),
'items': ['Asset', 'Asset Value Adjustment']
},
{
'items': ['Company']
},
{
'items': ['Journal Entry']
}
]
"fieldname": "finance_book",
"non_standard_fieldnames": {"Asset": "default_finance_book", "Company": "default_finance_book"},
"transactions": [
{"label": _("Assets"), "items": ["Asset", "Asset Value Adjustment"]},
{"items": ["Company"]},
{"items": ["Journal Entry"]},
],
}

View File

@@ -13,31 +13,30 @@ class TestFinanceBook(unittest.TestCase):
finance_book = create_finance_book()
# create jv entry
jv = make_journal_entry("_Test Bank - _TC",
"_Test Receivable - _TC", 100, save=False)
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
jv.accounts[1].update({
"party_type": "Customer",
"party": "_Test Customer"
})
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer"})
jv.finance_book = finance_book.finance_book_name
jv.submit()
# check the Finance Book in the GL Entry
gl_entries = frappe.get_all("GL Entry", fields=["name", "finance_book"],
filters={"voucher_type": "Journal Entry", "voucher_no": jv.name})
gl_entries = frappe.get_all(
"GL Entry",
fields=["name", "finance_book"],
filters={"voucher_type": "Journal Entry", "voucher_no": jv.name},
)
for gl_entry in gl_entries:
self.assertEqual(gl_entry.finance_book, finance_book.name)
def create_finance_book():
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
finance_book = frappe.get_doc({
"doctype": "Finance Book",
"finance_book_name": "_Test Finance Book"
}).insert()
finance_book = frappe.get_doc(
{"doctype": "Finance Book", "finance_book_name": "_Test Finance Book"}
).insert()
else:
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
return finance_book
return finance_book

View File

@@ -9,7 +9,9 @@ from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYearIncorrectDate(frappe.ValidationError): pass
class FiscalYearIncorrectDate(frappe.ValidationError):
pass
class FiscalYear(Document):
@frappe.whitelist()
@@ -22,19 +24,33 @@ class FiscalYear(Document):
# clear cache
frappe.clear_cache()
msgprint(_("{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect.").format(self.name))
msgprint(
_(
"{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
).format(self.name)
)
def validate(self):
self.validate_dates()
self.validate_overlap()
if not self.is_new():
year_start_end_dates = frappe.db.sql("""select year_start_date, year_end_date
from `tabFiscal Year` where name=%s""", (self.name))
year_start_end_dates = frappe.db.sql(
"""select year_start_date, year_end_date
from `tabFiscal Year` where name=%s""",
(self.name),
)
if year_start_end_dates:
if getdate(self.year_start_date) != year_start_end_dates[0][0] or getdate(self.year_end_date) != year_start_end_dates[0][1]:
frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
if (
getdate(self.year_start_date) != year_start_end_dates[0][0]
or getdate(self.year_end_date) != year_start_end_dates[0][1]
):
frappe.throw(
_(
"Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
)
)
def validate_dates(self):
if self.is_short_year:
@@ -43,14 +59,18 @@ class FiscalYear(Document):
return
if getdate(self.year_start_date) > getdate(self.year_end_date):
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate)
frappe.throw(
_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate,
)
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date:
frappe.throw(_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
FiscalYearIncorrectDate)
frappe.throw(
_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
FiscalYearIncorrectDate,
)
def on_update(self):
check_duplicate_fiscal_year(self)
@@ -59,11 +79,16 @@ class FiscalYear(Document):
def on_trash(self):
global_defaults = frappe.get_doc("Global Defaults")
if global_defaults.current_fiscal_year == self.name:
frappe.throw(_("You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings").format(self.name))
frappe.throw(
_(
"You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
).format(self.name)
)
frappe.cache().delete_value("fiscal_years")
def validate_overlap(self):
existing_fiscal_years = frappe.db.sql("""select name from `tabFiscal Year`
existing_fiscal_years = frappe.db.sql(
"""select name from `tabFiscal Year`
where (
(%(year_start_date)s between year_start_date and year_end_date)
or (%(year_end_date)s between year_start_date and year_end_date)
@@ -73,13 +98,18 @@ class FiscalYear(Document):
{
"year_start_date": self.year_start_date,
"year_end_date": self.year_end_date,
"name": self.name or "No Name"
}, as_dict=True)
"name": self.name or "No Name",
},
as_dict=True,
)
if existing_fiscal_years:
for existing in existing_fiscal_years:
company_for_existing = frappe.db.sql_list("""select company from `tabFiscal Year Company`
where parent=%s""", existing.name)
company_for_existing = frappe.db.sql_list(
"""select company from `tabFiscal Year Company`
where parent=%s""",
existing.name,
)
overlap = False
if not self.get("companies") or not company_for_existing:
@@ -90,20 +120,36 @@ class FiscalYear(Document):
overlap = True
if overlap:
frappe.throw(_("Year start date or end date is overlapping with {0}. To avoid please set company")
.format(existing.name), frappe.NameError)
frappe.throw(
_("Year start date or end date is overlapping with {0}. To avoid please set company").format(
existing.name
),
frappe.NameError,
)
@frappe.whitelist()
def check_duplicate_fiscal_year(doc):
year_start_end_dates = frappe.db.sql("""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""", (doc.name))
year_start_end_dates = frappe.db.sql(
"""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
(doc.name),
)
for fiscal_year, ysd, yed in year_start_end_dates:
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (not frappe.flags.in_test):
frappe.throw(_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(fiscal_year))
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
not frappe.flags.in_test
):
frappe.throw(
_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(
fiscal_year
)
)
@frappe.whitelist()
def auto_create_fiscal_year():
for d in frappe.db.sql("""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""):
for d in frappe.db.sql(
"""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
):
try:
current_fy = frappe.get_doc("Fiscal Year", d[0])
@@ -114,16 +160,14 @@ def auto_create_fiscal_year():
start_year = cstr(new_fy.year_start_date.year)
end_year = cstr(new_fy.year_end_date.year)
new_fy.year = start_year if start_year==end_year else (start_year + "-" + end_year)
new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
new_fy.auto_created = 1
new_fy.insert(ignore_permissions=True)
except frappe.NameError:
pass
def get_from_and_to_date(fiscal_year):
fields = [
"year_start_date as from_date",
"year_end_date as to_date"
]
fields = ["year_start_date as from_date", "year_end_date as to_date"]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)

View File

@@ -1,22 +1,15 @@
from frappe import _
def get_data():
return {
'fieldname': 'fiscal_year',
'transactions': [
"fieldname": "fiscal_year",
"transactions": [
{"label": _("Budgets"), "items": ["Budget"]},
{"label": _("References"), "items": ["Period Closing Voucher"]},
{
'label': _('Budgets'),
'items': ['Budget']
"label": _("Target Details"),
"items": ["Sales Person", "Sales Partner", "Territory", "Monthly Distribution"],
},
{
'label': _('References'),
'items': ['Period Closing Voucher']
},
{
'label': _('Target Details'),
'items': ['Sales Person', 'Sales Partner', 'Territory', 'Monthly Distribution']
}
]
],
}

View File

@@ -11,43 +11,48 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrect
test_ignore = ["Company"]
class TestFiscalYear(unittest.TestCase):
class TestFiscalYear(unittest.TestCase):
def test_extra_year(self):
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
fy = frappe.get_doc({
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2000",
"year_end_date": "2002-12-31",
"year_start_date": "2000-04-01"
})
fy = frappe.get_doc(
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2000",
"year_end_date": "2002-12-31",
"year_start_date": "2000-04-01",
}
)
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
def test_record_generator():
test_records = [
{
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31"
}
{
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31",
}
]
start = 2012
end = now_datetime().year + 5
for year in range(start, end):
test_records.append({
"doctype": "Fiscal Year",
"year": f"_Test Fiscal Year {year}",
"year_start_date": f"{year}-01-01",
"year_end_date": f"{year}-12-31"
})
test_records.append(
{
"doctype": "Fiscal Year",
"year": f"_Test Fiscal Year {year}",
"year_start_date": f"{year}-01-01",
"year_end_date": f"{year}-12-31",
}
)
return test_records
test_records = test_record_generator()

View File

@@ -26,6 +26,8 @@ from erpnext.exceptions import (
)
exclude_from_linked_with = True
class GLEntry(Document):
def autoname(self):
"""
@@ -33,6 +35,8 @@ class GLEntry(Document):
name will be changed using autoname options (in a scheduled job)
"""
self.name = frappe.generate_hash(txt="", length=10)
if self.meta.autoname == "hash":
self.to_rename = 0
def validate(self):
self.flags.ignore_submit_comment = True
@@ -56,14 +60,18 @@ class GLEntry(Document):
validate_frozen_account(self.account, adv_adj)
# Update outstanding amt on against voucher
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
and self.against_voucher and self.flags.update_outstanding == 'Yes'
and not frappe.flags.is_reverse_depr_entry):
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)
def check_mandatory(self):
mandatory = ['account','voucher_type','voucher_no','company']
mandatory = ["account", "voucher_type", "voucher_no", "company"]
for k in mandatory:
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
@@ -71,29 +79,40 @@ class GLEntry(Document):
if not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable":
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
.format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(
_("{0} {1}: Customer is required against Receivable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
)
elif account_type == "Payable":
frappe.throw(_("{0} {1}: Supplier is required against Payable account {2}")
.format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(
_("{0} {1}: Supplier is required against Payable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
)
# Zero value transaction is not allowed
if not (flt(self.debit, self.precision("debit")) or flt(self.credit, self.precision("credit"))):
frappe.throw(_("{0} {1}: Either debit or credit amount is required for {2}")
.format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(
_("{0} {1}: Either debit or credit amount is required for {2}").format(
self.voucher_type, self.voucher_no, self.account
)
)
def pl_must_have_cost_center(self):
"""Validate that profit and loss type account GL entries have a cost center."""
if self.cost_center or self.voucher_type == 'Period Closing Voucher':
if self.cost_center or self.voucher_type == "Period Closing Voucher":
return
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
self.voucher_type, self.voucher_no, self.account)
self.voucher_type, self.voucher_no, self.account
)
msg += " "
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
self.voucher_type)
msg += _(
"Please set the cost center field in {0} or setup a default Cost Center for the Company."
).format(self.voucher_type)
frappe.throw(msg, title=_("Missing Cost Center"))
@@ -101,17 +120,31 @@ class GLEntry(Document):
account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
if account_type == "Profit and Loss" \
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
if (
account_type == "Profit and Loss"
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.")
.format(dimension.label, self.account))
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
)
if account_type == "Balance Sheet" \
and self.company == dimension.company and dimension.mandatory_for_bs and not dimension.disabled:
if (
account_type == "Balance Sheet"
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
.format(dimension.label, self.account))
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
)
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
@@ -120,56 +153,97 @@ class GLEntry(Document):
account = key[1]
if self.account == account:
if value['is_mandatory'] and not self.get(dimension):
frappe.throw(_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)
if value['allow_or_restrict'] == 'Allow':
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
def check_pl_account(self):
if self.is_opening=='Yes' and \
frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss":
frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
.format(self.voucher_type, self.voucher_no, self.account))
if (
self.is_opening == "Yes"
and frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss"
and not self.is_cancelled
):
frappe.throw(
_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry").format(
self.voucher_type, self.voucher_no, self.account
)
)
def validate_account_details(self, adv_adj):
"""Account must be ledger, active and not freezed"""
ret = frappe.db.sql("""select is_group, docstatus, company
from tabAccount where name=%s""", self.account, as_dict=1)[0]
ret = frappe.db.sql(
"""select is_group, docstatus, company
from tabAccount where name=%s""",
self.account,
as_dict=1,
)[0]
if ret.is_group==1:
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
.format(self.voucher_type, self.voucher_no, self.account))
if ret.is_group == 1:
frappe.throw(
_(
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
).format(self.voucher_type, self.voucher_no, self.account)
)
if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive")
.format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus == 2:
frappe.throw(
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
)
if ret.company != self.company:
frappe.throw(_("{0} {1}: Account {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.account, self.company))
frappe.throw(
_("{0} {1}: Account {2} does not belong to Company {3}").format(
self.voucher_type, self.voucher_no, self.account, self.company
)
)
def validate_cost_center(self):
if not self.cost_center: return
if not self.cost_center:
return
is_group, company = frappe.get_cached_value('Cost Center',
self.cost_center, ['is_group', 'company'])
is_group, company = frappe.get_cached_value(
"Cost Center", self.cost_center, ["is_group", "company"]
)
if company != self.company:
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
frappe.throw(
_("{0} {1}: Cost Center {2} does not belong to Company {3}").format(
self.voucher_type, self.voucher_no, self.cost_center, self.company
)
)
if (self.voucher_type != 'Period Closing Voucher' and is_group):
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
if self.voucher_type != "Period Closing Voucher" and is_group:
frappe.throw(
_(
"""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions"""
).format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))
)
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)
@@ -182,9 +256,12 @@ class GLEntry(Document):
self.account_currency = account_currency or company_currency
if account_currency != self.account_currency:
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
.format(self.voucher_type, self.voucher_no, self.account,
(account_currency or company_currency)), InvalidAccountCurrency)
frappe.throw(
_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}").format(
self.voucher_type, self.voucher_no, self.account, (account_currency or company_currency)
),
InvalidAccountCurrency,
)
if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
@@ -193,51 +270,80 @@ class GLEntry(Document):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
if balance_must_be:
balance = frappe.db.sql("""select sum(debit) - sum(credit)
from `tabGL Entry` where account = %s""", account)[0][0]
balance = frappe.db.sql(
"""select sum(debit) - sum(credit)
from `tabGL Entry` where account = %s""",
account,
)[0][0]
if (balance_must_be=="Debit" and flt(balance) < 0) or \
(balance_must_be=="Credit" and flt(balance) > 0):
frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be)))
if (balance_must_be == "Debit" and flt(balance) < 0) or (
balance_must_be == "Credit" and flt(balance) > 0
):
frappe.throw(
_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))
)
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
def update_outstanding_amt(
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
):
if party_type and party:
party_condition = " and party_type={0} and party={1}"\
.format(frappe.db.escape(party_type), frappe.db.escape(party))
party_condition = " and party_type={0} and party={1}".format(
frappe.db.escape(party_type), frappe.db.escape(party)
)
else:
party_condition = ""
if against_voucher_type == "Sales Invoice":
party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
account_condition = "and account in ({0}, {1})".format(frappe.db.escape(account), frappe.db.escape(party_account))
account_condition = "and account in ({0}, {1})".format(
frappe.db.escape(account), frappe.db.escape(party_account)
)
else:
account_condition = " and account = {0}".format(frappe.db.escape(account))
# get final outstanding amt
bal = flt(frappe.db.sql("""
bal = flt(
frappe.db.sql(
"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s
and voucher_type != 'Invoice Discounting'
{0} {1}""".format(party_condition, account_condition),
(against_voucher_type, against_voucher))[0][0] or 0.0)
{0} {1}""".format(
party_condition, account_condition
),
(against_voucher_type, against_voucher),
)[0][0]
or 0.0
)
if against_voucher_type == 'Purchase Invoice':
if against_voucher_type == "Purchase Invoice":
bal = -bal
elif against_voucher_type == "Journal Entry":
against_voucher_amount = flt(frappe.db.sql("""
against_voucher_amount = flt(
frappe.db.sql(
"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
and account = %s and (against_voucher is null or against_voucher='') {0}"""
.format(party_condition), (against_voucher, account))[0][0])
and account = %s and (against_voucher is null or against_voucher='') {0}""".format(
party_condition
),
(against_voucher, account),
)[0][0]
)
if not against_voucher_amount:
frappe.throw(_("Against Journal Entry {0} is already adjusted against some other voucher")
.format(against_voucher))
frappe.throw(
_("Against Journal Entry {0} is already adjusted against some other voucher").format(
against_voucher
)
)
bal = against_voucher_amount + bal
if against_voucher_amount < 0:
@@ -245,44 +351,51 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
# Validation : Outstanding can not be negative for JV
if bal < 0 and not on_cancel:
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
frappe.throw(
_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))
)
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
ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, 'outstanding_amount', bal)
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)
ref_doc.set_status(update=True)
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == 'Yes' and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
'frozen_accounts_modifier')
if frozen_account == "Yes" and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", None, "frozen_accounts_modifier"
)
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
elif frozen_accounts_modifier not in frappe.get_roles():
frappe.throw(_("Not authorized to edit frozen Account {0}").format(account))
def update_against_account(voucher_type, voucher_no):
entries = frappe.db.get_all("GL Entry",
entries = frappe.db.get_all(
"GL Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
fields=["name", "party", "against", "debit", "credit", "account", "company"])
fields=["name", "party", "against", "debit", "credit", "account", "company"],
)
if not entries:
return
company_currency = erpnext.get_company_currency(entries[0].company)
precision = get_field_precision(frappe.get_meta("GL Entry")
.get_field("debit"), company_currency)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
accounts_debited, accounts_credited = [], []
for d in entries:
if flt(d.debit, precision) > 0: accounts_debited.append(d.party or d.account)
if flt(d.credit, precision) > 0: accounts_credited.append(d.party or d.account)
if flt(d.debit, precision) > 0:
accounts_debited.append(d.party or d.account)
if flt(d.credit, precision) > 0:
accounts_credited.append(d.party or d.account)
for d in entries:
if flt(d.debit, precision) > 0:
@@ -293,14 +406,17 @@ def update_against_account(voucher_type, voucher_no):
if d.against != new_against:
frappe.db.set_value("GL Entry", d.name, "against", new_against)
def on_doctype_update():
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]:
rename_temporarily_named_docs(doctype)
def rename_temporarily_named_docs(doctype):
"""Rename temporarily named docs using autoname options"""
docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000)
@@ -311,5 +427,5 @@ def rename_temporarily_named_docs(doctype):
frappe.db.sql(
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
(newname, oldname),
auto_commit=True
auto_commit=True,
)

View File

@@ -14,48 +14,68 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestGLEntry(unittest.TestCase):
def test_round_off_entry(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
frappe.db.set_value(
"Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC"
)
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 100, "_Test Cost Center - _TC", submit=False)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
100,
"_Test Cost Center - _TC",
submit=False,
)
jv.get("accounts")[0].debit = 100.01
jv.flags.ignore_validate = True
jv.submit()
round_off_entry = frappe.db.sql("""select name from `tabGL Entry`
round_off_entry = frappe.db.sql(
"""select name from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no = %s
and account='_Test Write Off - _TC' and cost_center='_Test Cost Center - _TC'
and debit = 0 and credit = '.01'""", jv.name)
and debit = 0 and credit = '.01'""",
jv.name,
)
self.assertTrue(round_off_entry)
def test_rename_entries(self):
je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True)
je = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True
)
rename_gle_sle_docs()
naming_series = parse_naming_series(parts=frappe.get_meta("GL Entry").autoname.split(".")[:-1])
je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True)
je = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True
)
gl_entries = frappe.get_all("GL Entry",
gl_entries = frappe.get_all(
"GL Entry",
fields=["name", "to_rename"],
filters={"voucher_type": "Journal Entry", "voucher_no": je.name},
order_by="creation"
order_by="creation",
)
self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries))
old_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
old_naming_series_current_value = frappe.db.sql(
"SELECT current from tabSeries where name = %s", naming_series
)[0][0]
rename_gle_sle_docs()
new_gl_entries = frappe.get_all("GL Entry",
new_gl_entries = frappe.get_all(
"GL Entry",
fields=["name", "to_rename"],
filters={"voucher_type": "Journal Entry", "voucher_no": je.name},
order_by="creation"
order_by="creation",
)
self.assertTrue(all(entry.to_rename == 0 for entry in new_gl_entries))
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
new_naming_series_current_value = frappe.db.sql(
"SELECT current from tabSeries where name = %s", naming_series
)[0][0]
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)

View File

@@ -10,6 +10,7 @@
"sgst_account",
"igst_account",
"cess_account",
"utgst_account",
"is_reverse_charge_account"
],
"fields": [
@@ -64,12 +65,18 @@
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Reverse Charge Account"
},
{
"fieldname": "utgst_account",
"fieldtype": "Link",
"label": "UTGST Account",
"options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-09 12:30:25.889993",
"modified": "2022-04-07 12:59:14.039768",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST Account",
@@ -78,5 +85,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -33,19 +33,32 @@ class InvoiceDiscounting(AccountsController):
frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting"))
def validate_invoices(self):
discounted_invoices = [record.sales_invoice for record in
frappe.get_all("Discounted Invoice",fields=["sales_invoice"], filters={"docstatus":1})]
discounted_invoices = [
record.sales_invoice
for record in frappe.get_all(
"Discounted Invoice", fields=["sales_invoice"], filters={"docstatus": 1}
)
]
for record in self.invoices:
if record.sales_invoice in discounted_invoices:
frappe.throw(_("Row({0}): {1} is already discounted in {2}")
.format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)))
frappe.throw(
_("Row({0}): {1} is already discounted in {2}").format(
record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)
)
)
actual_outstanding = frappe.db.get_value("Sales Invoice", record.sales_invoice,"outstanding_amount")
if record.outstanding_amount > actual_outstanding :
frappe.throw(_
("Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}").format(
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
actual_outstanding = frappe.db.get_value(
"Sales Invoice", record.sales_invoice, "outstanding_amount"
)
if record.outstanding_amount > actual_outstanding:
frappe.throw(
_(
"Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
).format(
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
)
)
def calculate_total_amount(self):
self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices)
@@ -73,24 +86,21 @@ class InvoiceDiscounting(AccountsController):
self.status = "Cancelled"
if cancel:
self.db_set('status', self.status, update_modified = True)
self.db_set("status", self.status, update_modified=True)
def update_sales_invoice(self):
for d in self.invoices:
if self.docstatus == 1:
is_discounted = 1
else:
discounted_invoice = frappe.db.exists({
"doctype": "Discounted Invoice",
"sales_invoice": d.sales_invoice,
"docstatus": 1
})
discounted_invoice = frappe.db.exists(
{"doctype": "Discounted Invoice", "sales_invoice": d.sales_invoice, "docstatus": 1}
)
is_discounted = 1 if discounted_invoice else 0
frappe.db.set_value("Sales Invoice", d.sales_invoice, "is_discounted", is_discounted)
def make_gl_entries(self):
company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
gl_entries = []
invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
@@ -102,135 +112,182 @@ class InvoiceDiscounting(AccountsController):
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
if d.outstanding_amount:
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
d.precision("outstanding_amount"))
ar_credit_account_currency = frappe.get_cached_value("Account", self.accounts_receivable_credit, "currency")
outstanding_in_company_currency = flt(
d.outstanding_amount * inv.conversion_rate, d.precision("outstanding_amount")
)
ar_credit_account_currency = frappe.get_cached_value(
"Account", self.accounts_receivable_credit, "currency"
)
gl_entries.append(self.get_gl_dict({
"account": inv.debit_to,
"party_type": "Customer",
"party": d.customer,
"against": self.accounts_receivable_credit,
"credit": outstanding_in_company_currency,
"credit_in_account_currency": outstanding_in_company_currency \
if inv.party_account_currency==company_currency else d.outstanding_amount,
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
}, inv.party_account_currency, item=inv))
gl_entries.append(
self.get_gl_dict(
{
"account": inv.debit_to,
"party_type": "Customer",
"party": d.customer,
"against": self.accounts_receivable_credit,
"credit": outstanding_in_company_currency,
"credit_in_account_currency": outstanding_in_company_currency
if inv.party_account_currency == company_currency
else d.outstanding_amount,
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice",
},
inv.party_account_currency,
item=inv,
)
)
gl_entries.append(self.get_gl_dict({
"account": self.accounts_receivable_credit,
"party_type": "Customer",
"party": d.customer,
"against": inv.debit_to,
"debit": outstanding_in_company_currency,
"debit_in_account_currency": outstanding_in_company_currency \
if ar_credit_account_currency==company_currency else d.outstanding_amount,
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
}, ar_credit_account_currency, item=inv))
gl_entries.append(
self.get_gl_dict(
{
"account": self.accounts_receivable_credit,
"party_type": "Customer",
"party": d.customer,
"against": inv.debit_to,
"debit": outstanding_in_company_currency,
"debit_in_account_currency": outstanding_in_company_currency
if ar_credit_account_currency == company_currency
else d.outstanding_amount,
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice",
},
ar_credit_account_currency,
item=inv,
)
)
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No")
@frappe.whitelist()
def create_disbursement_entry(self):
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Journal Entry'
je.voucher_type = "Journal Entry"
je.company = self.company
je.remark = 'Loan Disbursement entry against Invoice Discounting: ' + self.name
je.remark = "Loan Disbursement entry against Invoice Discounting: " + self.name
je.append("accounts", {
"account": self.bank_account,
"debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
})
je.append(
"accounts",
{
"account": self.bank_account,
"debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company),
},
)
if self.bank_charges:
je.append("accounts", {
"account": self.bank_charges_account,
"debit_in_account_currency": flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
})
je.append(
"accounts",
{
"account": self.bank_charges_account,
"debit_in_account_currency": flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company),
},
)
je.append("accounts", {
"account": self.short_term_loan,
"credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name
})
je.append(
"accounts",
{
"account": self.short_term_loan,
"credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
},
)
for d in self.invoices:
je.append("accounts", {
"account": self.accounts_receivable_discounted,
"debit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer
})
je.append(
"accounts",
{
"account": self.accounts_receivable_discounted,
"debit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer,
},
)
je.append("accounts", {
"account": self.accounts_receivable_credit,
"credit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer
})
je.append(
"accounts",
{
"account": self.accounts_receivable_credit,
"credit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer,
},
)
return je
@frappe.whitelist()
def close_loan(self):
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Journal Entry'
je.voucher_type = "Journal Entry"
je.company = self.company
je.remark = 'Loan Settlement entry against Invoice Discounting: ' + self.name
je.remark = "Loan Settlement entry against Invoice Discounting: " + self.name
je.append("accounts", {
"account": self.short_term_loan,
"debit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
})
je.append(
"accounts",
{
"account": self.short_term_loan,
"debit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
},
)
je.append("accounts", {
"account": self.bank_account,
"credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company)
})
je.append(
"accounts",
{
"account": self.bank_account,
"credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
},
)
if getdate(self.loan_end_date) > getdate(nowdate()):
for d in self.invoices:
outstanding_amount = frappe.db.get_value("Sales Invoice", d.sales_invoice, "outstanding_amount")
outstanding_amount = frappe.db.get_value(
"Sales Invoice", d.sales_invoice, "outstanding_amount"
)
if flt(outstanding_amount) > 0:
je.append("accounts", {
"account": self.accounts_receivable_discounted,
"credit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer
})
je.append(
"accounts",
{
"account": self.accounts_receivable_discounted,
"credit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer,
},
)
je.append("accounts", {
"account": self.accounts_receivable_unpaid,
"debit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer
})
je.append(
"accounts",
{
"account": self.accounts_receivable_unpaid,
"debit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
"party": d.customer,
},
)
return je
@frappe.whitelist()
def get_invoices(filters):
filters = frappe._dict(json.loads(filters))
@@ -250,7 +307,8 @@ def get_invoices(filters):
if cond:
where_condition += " and " + " and ".join(cond)
return frappe.db.sql("""
return frappe.db.sql(
"""
select
name as sales_invoice,
customer,
@@ -264,17 +322,26 @@ def get_invoices(filters):
%s
and not exists(select di.name from `tabDiscounted Invoice` di
where di.docstatus=1 and di.sales_invoice=si.name)
""" % where_condition, filters, as_dict=1)
"""
% where_condition,
filters,
as_dict=1,
)
def get_party_account_based_on_invoice_discounting(sales_invoice):
party_account = None
invoice_discounting = frappe.db.sql("""
invoice_discounting = frappe.db.sql(
"""
select par.accounts_receivable_discounted, par.accounts_receivable_unpaid, par.status
from `tabInvoice Discounting` par, `tabDiscounted Invoice` ch
where par.name=ch.parent
and par.docstatus=1
and ch.sales_invoice = %s
""", (sales_invoice), as_dict=1)
""",
(sales_invoice),
as_dict=1,
)
if invoice_discounting:
if invoice_discounting[0].status == "Disbursed":
party_account = invoice_discounting[0].accounts_receivable_discounted

View File

@@ -1,21 +1,12 @@
from frappe import _
def get_data():
return {
'fieldname': 'reference_name',
'internal_links': {
'Sales Invoice': ['invoices', 'sales_invoice']
},
'transactions': [
{
'label': _('Reference'),
'items': ['Sales Invoice']
},
{
'label': _('Payment'),
'items': ['Payment Entry', 'Journal Entry']
}
]
"fieldname": "reference_name",
"internal_links": {"Sales Invoice": ["invoices", "sales_invoice"]},
"transactions": [
{"label": _("Reference"), "items": ["Sales Invoice"]},
{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]},
],
}

View File

@@ -14,52 +14,74 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_
class TestInvoiceDiscounting(unittest.TestCase):
def setUp(self):
self.ar_credit = create_account(account_name="_Test Accounts Receivable Credit", parent_account = "Accounts Receivable - _TC", company="_Test Company")
self.ar_discounted = create_account(account_name="_Test Accounts Receivable Discounted", parent_account = "Accounts Receivable - _TC", company="_Test Company")
self.ar_unpaid = create_account(account_name="_Test Accounts Receivable Unpaid", parent_account = "Accounts Receivable - _TC", company="_Test Company")
self.short_term_loan = create_account(account_name="_Test Short Term Loan", parent_account = "Source of Funds (Liabilities) - _TC", company="_Test Company")
self.bank_account = create_account(account_name="_Test Bank 2", parent_account = "Bank Accounts - _TC", company="_Test Company")
self.bank_charges_account = create_account(account_name="_Test Bank Charges Account", parent_account = "Expenses - _TC", company="_Test Company")
self.ar_credit = create_account(
account_name="_Test Accounts Receivable Credit",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
)
self.ar_discounted = create_account(
account_name="_Test Accounts Receivable Discounted",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
)
self.ar_unpaid = create_account(
account_name="_Test Accounts Receivable Unpaid",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
)
self.short_term_loan = create_account(
account_name="_Test Short Term Loan",
parent_account="Source of Funds (Liabilities) - _TC",
company="_Test Company",
)
self.bank_account = create_account(
account_name="_Test Bank 2", parent_account="Bank Accounts - _TC", company="_Test Company"
)
self.bank_charges_account = create_account(
account_name="_Test Bank Charges Account",
parent_account="Expenses - _TC",
company="_Test Company",
)
frappe.db.set_value("Company", "_Test Company", "default_bank_account", self.bank_account)
def test_total_amount(self):
inv1 = create_sales_invoice(rate=200)
inv2 = create_sales_invoice(rate=500)
inv_disc = create_invoice_discounting([inv1.name, inv2.name],
inv_disc = create_invoice_discounting(
[inv1.name, inv2.name],
do_not_submit=True,
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account
)
bank_account=self.bank_account,
)
self.assertEqual(inv_disc.total_amount, 700)
def test_gl_entries_in_base_currency(self):
inv = create_sales_invoice(rate=200)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account
)
bank_account=self.bank_account,
)
gle = get_gl_entries("Invoice Discounting", inv_disc.name)
expected_gle = {
inv.debit_to: [0.0, 200],
self.ar_credit: [200, 0.0]
}
expected_gle = {inv.debit_to: [0.0, 200], self.ar_credit: [200, 0.0]}
for i, gle in enumerate(gle):
self.assertEqual([gle.debit, gle.credit], expected_gle.get(gle.account))
def test_loan_on_submit(self):
inv = create_sales_invoice(rate=300)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -67,28 +89,33 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
period=60
)
period=60,
)
self.assertEqual(inv_disc.status, "Sanctioned")
self.assertEqual(inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period))
self.assertEqual(
inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period)
)
def test_on_disbursed(self):
inv = create_sales_invoice(rate=500)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
bank_charges=100
)
bank_charges=100,
)
je = inv_disc.create_disbursement_entry()
self.assertEqual(je.accounts[0].account, self.bank_account)
self.assertEqual(je.accounts[0].debit_in_account_currency, flt(inv_disc.total_amount) - flt(inv_disc.bank_charges))
self.assertEqual(
je.accounts[0].debit_in_account_currency,
flt(inv_disc.total_amount) - flt(inv_disc.bank_charges),
)
self.assertEqual(je.accounts[1].account, self.bank_charges_account)
self.assertEqual(je.accounts[1].debit_in_account_currency, flt(inv_disc.bank_charges))
@@ -102,7 +129,6 @@ class TestInvoiceDiscounting(unittest.TestCase):
self.assertEqual(je.accounts[4].account, self.ar_credit)
self.assertEqual(je.accounts[4].credit_in_account_currency, flt(inv.outstanding_amount))
je.posting_date = nowdate()
je.submit()
@@ -114,7 +140,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_after_loan_period(self):
inv = create_sales_invoice(rate=600)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -122,8 +149,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
period=60
)
period=60,
)
je1 = inv_disc.create_disbursement_entry()
je1.posting_date = nowdate()
@@ -151,7 +178,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_after_loan_period_after_inv_payment(self):
inv = create_sales_invoice(rate=600)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -159,8 +187,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=nowdate(),
period=60
)
period=60,
)
je1 = inv_disc.create_disbursement_entry()
je1.posting_date = nowdate()
@@ -183,7 +211,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
def test_on_close_before_loan_period(self):
inv = create_sales_invoice(rate=700)
inv_disc = create_invoice_discounting([inv.name],
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
@@ -191,7 +220,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
start=add_days(nowdate(), -80),
period=60
period=60,
)
je1 = inv_disc.create_disbursement_entry()
@@ -209,16 +238,17 @@ class TestInvoiceDiscounting(unittest.TestCase):
self.assertEqual(je2.accounts[1].credit_in_account_currency, flt(inv_disc.total_amount))
def test_make_payment_before_loan_period(self):
#it has problem
# it has problem
inv = create_sales_invoice(rate=700)
inv_disc = create_invoice_discounting([inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account
)
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
)
je = inv_disc.create_disbursement_entry()
inv_disc.reload()
je.posting_date = nowdate()
@@ -232,26 +262,31 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_discounted)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
def test_make_payment_before_after_period(self):
#it has problem
# it has problem
inv = create_sales_invoice(rate=700)
inv_disc = create_invoice_discounting([inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
loan_start_date=add_days(nowdate(), -10),
period=5
)
inv_disc = create_invoice_discounting(
[inv.name],
accounts_receivable_credit=self.ar_credit,
accounts_receivable_discounted=self.ar_discounted,
accounts_receivable_unpaid=self.ar_unpaid,
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
loan_start_date=add_days(nowdate(), -10),
period=5,
)
je = inv_disc.create_disbursement_entry()
inv_disc.reload()
je.posting_date = nowdate()
@@ -269,9 +304,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_unpaid)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
@@ -287,17 +326,15 @@ def create_invoice_discounting(invoices, **args):
inv_disc.accounts_receivable_credit = args.accounts_receivable_credit
inv_disc.accounts_receivable_discounted = args.accounts_receivable_discounted
inv_disc.accounts_receivable_unpaid = args.accounts_receivable_unpaid
inv_disc.short_term_loan=args.short_term_loan
inv_disc.bank_charges_account=args.bank_charges_account
inv_disc.bank_account=args.bank_account
inv_disc.short_term_loan = args.short_term_loan
inv_disc.bank_charges_account = args.bank_charges_account
inv_disc.bank_account = args.bank_account
inv_disc.loan_start_date = args.start or nowdate()
inv_disc.loan_period = args.period or 30
inv_disc.bank_charges = flt(args.bank_charges)
for d in invoices:
inv_disc.append("invoices", {
"sales_invoice": d
})
inv_disc.append("invoices", {"sales_invoice": d})
inv_disc.insert()
if not args.do_not_submit:

View File

@@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2018-11-22 22:45:00.370913",
"creation": "2022-01-19 01:09:13.297137",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
@@ -10,6 +10,9 @@
"field_order": [
"title",
"company",
"column_break_3",
"disabled",
"section_break_5",
"taxes"
],
"fields": [
@@ -36,10 +39,24 @@
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
}
],
"links": [],
"modified": "2021-03-08 19:50:21.416513",
"modified": "2022-01-18 21:11:23.105589",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@@ -82,6 +99,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@@ -13,20 +13,28 @@ class ItemTaxTemplate(Document):
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value('Company', self.company, 'abbr')
self.name = '{0} - {1}'.format(self.title, abbr)
abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = "{0} - {1}".format(self.title, abbr)
def validate_tax_accounts(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""
check_list = []
for d in self.get('taxes'):
for d in self.get("taxes"):
if d.tax_type:
account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation']:
if account_type not in [
"Tax",
"Chargeable",
"Income Account",
"Expense Account",
"Expenses Included In Valuation",
]:
frappe.throw(
_("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(
d.idx))
_(
"Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable"
).format(d.idx)
)
else:
if d.tax_type in check_list:
frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))

View File

@@ -1,26 +1,13 @@
from frappe import _
def get_data():
return {
'fieldname': 'item_tax_template',
'transactions': [
{
'label': _('Pre Sales'),
'items': ['Quotation', 'Supplier Quotation']
},
{
'label': _('Sales'),
'items': ['Sales Invoice', 'Sales Order', 'Delivery Note']
},
{
'label': _('Purchase'),
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
},
{
'label': _('Stock'),
'items': ['Item Groups', 'Item']
}
]
"fieldname": "item_tax_template",
"transactions": [
{"label": _("Pre Sales"), "items": ["Quotation", "Supplier Quotation"]},
{"label": _("Sales"), "items": ["Sales Invoice", "Sales Order", "Delivery Note"]},
{"label": _("Purchase"), "items": ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]},
{"label": _("Stock"), "items": ["Item Groups", "Item"]},
],
}

View File

@@ -8,6 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
},
refresh: function(frm) {

View File

@@ -3,7 +3,7 @@
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-03-25 10:53:52",
"creation": "2022-01-25 10:29:58.717206",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
@@ -13,6 +13,7 @@
"voucher_type",
"naming_series",
"finance_book",
"process_deferred_accounting",
"reversal_of",
"tax_withholding_category",
"column_break1",
@@ -524,13 +525,20 @@
"label": "Reversal Of",
"options": "Journal Entry",
"read_only": 1
},
{
"fieldname": "process_deferred_accounting",
"fieldtype": "Link",
"label": "Process Deferred Accounting",
"options": "Process Deferred Accounting",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2022-01-04 13:39:36.485954",
"modified": "2022-04-06 17:18:46.865259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
@@ -578,6 +586,7 @@
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

File diff suppressed because it is too large Load Diff

View File

@@ -39,14 +39,25 @@ class TestJournalEntry(unittest.TestCase):
test_voucher.submit()
if test_voucher.doctype == "Journal Entry":
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
self.assertTrue(
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s""",
("_Test Receivable - _TC", test_voucher.name)))
("_Test Receivable - _TC", test_voucher.name),
)
)
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s""", (test_voucher.doctype, test_voucher.name)))
self.assertFalse(
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s""",
(test_voucher.doctype, test_voucher.name),
)
)
base_jv.get("accounts")[0].is_advance = "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
base_jv.get("accounts")[0].is_advance = (
"Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
)
base_jv.get("accounts")[0].set("reference_type", test_voucher.doctype)
base_jv.get("accounts")[0].set("reference_name", test_voucher.name)
base_jv.insert()
@@ -54,18 +65,28 @@ class TestJournalEntry(unittest.TestCase):
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {0}=400""".format(dr_or_cr),
(submitted_voucher.doctype, submitted_voucher.name)))
self.assertTrue(
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {0}=400""".format(
dr_or_cr
),
(submitted_voucher.doctype, submitted_voucher.name),
)
)
if base_jv.get("accounts")[0].is_advance == "Yes":
self.advance_paid_testcase(base_jv, submitted_voucher, dr_or_cr)
self.cancel_against_voucher_testcase(submitted_voucher)
def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr):
#Test advance paid field
advance_paid = frappe.db.sql("""select advance_paid from `tab%s`
where name=%s""" % (test_voucher.doctype, '%s'), (test_voucher.name))
# Test advance paid field
advance_paid = frappe.db.sql(
"""select advance_paid from `tab%s`
where name=%s"""
% (test_voucher.doctype, "%s"),
(test_voucher.name),
)
payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr)
self.assertTrue(flt(advance_paid[0][0]) == flt(payment_against_order))
@@ -74,13 +95,19 @@ class TestJournalEntry(unittest.TestCase):
if test_voucher.doctype == "Journal Entry":
# if test_voucher is a Journal Entry, test cancellation of test_voucher
test_voucher.cancel()
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_type='Journal Entry' and reference_name=%s""", test_voucher.name))
self.assertFalse(
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where reference_type='Journal Entry' and reference_name=%s""",
test_voucher.name,
)
)
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0)
frappe.db.set_value(
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
)
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
@@ -89,7 +116,10 @@ class TestJournalEntry(unittest.TestCase):
stock_account = get_inventory_account(company)
from erpnext.accounts.utils import get_stock_and_account_balance
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
stock_account, nowdate(), company
)
diff = flt(account_bal) - flt(stock_bal)
if not diff:
@@ -98,19 +128,25 @@ class TestJournalEntry(unittest.TestCase):
jv = frappe.new_doc("Journal Entry")
jv.company = company
jv.posting_date = nowdate()
jv.append("accounts", {
"account": stock_account,
"cost_center": "Main - TCP1",
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
"credit_in_account_currency": diff if diff > 0 else 0
})
jv.append(
"accounts",
{
"account": stock_account,
"cost_center": "Main - TCP1",
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
"credit_in_account_currency": diff if diff > 0 else 0,
},
)
jv.append("accounts", {
"account": "Stock Adjustment - TCP1",
"cost_center": "Main - TCP1",
"debit_in_account_currency": diff if diff > 0 else 0,
"credit_in_account_currency": 0 if diff > 0 else abs(diff)
})
jv.append(
"accounts",
{
"account": "Stock Adjustment - TCP1",
"cost_center": "Main - TCP1",
"debit_in_account_currency": diff if diff > 0 else 0,
"credit_in_account_currency": 0 if diff > 0 else abs(diff),
},
)
jv.insert()
if account_bal == stock_bal:
@@ -121,16 +157,21 @@ class TestJournalEntry(unittest.TestCase):
jv.cancel()
def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC",
"_Test Bank - _TC", 100, exchange_rate=50, save=False)
jv = make_journal_entry(
"_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False
)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", jv.name, as_dict=1)
order by account asc""",
jv.name,
as_dict=1,
)
self.assertTrue(gl_entries)
@@ -140,33 +181,42 @@ class TestJournalEntry(unittest.TestCase):
"debit": 5000,
"debit_in_account_currency": 100,
"credit": 0,
"credit_in_account_currency": 0
"credit_in_account_currency": 0,
},
"_Test Bank - _TC": {
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000
}
"credit_in_account_currency": 5000,
},
}
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
jv.cancel()
gle = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", jv.name)
gle = frappe.db.sql(
"""select name from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""",
jv.name,
)
self.assertFalse(gle)
def test_reverse_journal_entry(self):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
jv = make_journal_entry("_Test Bank USD - _TC",
"Sales - _TC", 100, exchange_rate=50, save=False)
jv = make_journal_entry("_Test Bank USD - _TC", "Sales - _TC", 100, exchange_rate=50, save=False)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.get("accounts")[1].exchange_rate = 1
@@ -176,15 +226,17 @@ class TestJournalEntry(unittest.TestCase):
rjv.posting_date = nowdate()
rjv.submit()
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", rjv.name, as_dict=1)
order by account asc""",
rjv.name,
as_dict=1,
)
self.assertTrue(gl_entries)
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
@@ -199,44 +251,38 @@ class TestJournalEntry(unittest.TestCase):
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
}
},
}
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
jv = make_journal_entry("_Test Bank USD - _TC",
"_Test Receivable USD - _TC", 100, save=False)
jv = make_journal_entry("_Test Bank USD - _TC", "_Test Receivable USD - _TC", 100, save=False)
jv.accounts[1].update({
"party_type": "Customer",
"party": "_Test Customer USD"
})
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
jv.submit()
# create jv in USD, but account currency in INR
jv = make_journal_entry("_Test Bank - _TC",
"_Test Receivable - _TC", 100, save=False)
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
jv.accounts[1].update({
"party_type": "Customer",
"party": "_Test Customer USD"
})
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
self.assertRaises(InvalidAccountCurrency, jv.submit)
# back in USD
jv = make_journal_entry("_Test Bank USD - _TC",
"_Test Receivable USD - _TC", 100, save=False)
jv = make_journal_entry("_Test Bank USD - _TC", "_Test Receivable USD - _TC", 100, save=False)
jv.accounts[1].update({
"party_type": "Customer",
"party": "_Test Customer USD"
})
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
jv.submit()
@@ -245,13 +291,27 @@ class TestJournalEntry(unittest.TestCase):
frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1)
jv = make_journal_entry("Sales Expenses - _TC", "Buildings - _TC", 100, posting_date=nowdate(), cost_center = "Main - _TC", save=False)
jv = make_journal_entry(
"Sales Expenses - _TC",
"Buildings - _TC",
100,
posting_date=nowdate(),
cost_center="Main - _TC",
save=False,
)
jv.voucher_type = "Inter Company Journal Entry"
jv.multi_currency = 0
jv.insert()
jv.submit()
jv1 = make_journal_entry("Sales Expenses - _TC1", "Buildings - _TC1", 100, posting_date=nowdate(), cost_center = "Main - _TC1", save=False)
jv1 = make_journal_entry(
"Sales Expenses - _TC1",
"Buildings - _TC1",
100,
posting_date=nowdate(),
cost_center="Main - _TC1",
save=False,
)
jv1.inter_company_journal_entry_reference = jv.name
jv1.company = "_Test Company 1"
jv1.voucher_type = "Inter Company Journal Entry"
@@ -273,9 +333,12 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
jv = make_journal_entry(
"_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center=cost_center, save=False
)
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
jv.cheque_no = "112233"
@@ -284,17 +347,17 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
expected_values = {
"_Test Cash - _TC": {
"cost_center": cost_center
},
"_Test Bank - _TC": {
"cost_center": cost_center
}
"_Test Cash - _TC": {"cost_center": cost_center},
"_Test Bank - _TC": {"cost_center": cost_center},
}
gl_entries = frappe.db.sql("""select account, cost_center, debit, credit
gl_entries = frappe.db.sql(
"""select account, cost_center, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", jv.name, as_dict=1)
order by account asc""",
jv.name,
as_dict=1,
)
self.assertTrue(gl_entries)
@@ -305,11 +368,13 @@ class TestJournalEntry(unittest.TestCase):
from erpnext.projects.doctype.project.test_project import make_project
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
project = make_project({
'project_name': 'Journal Entry Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
project = make_project(
{
"project_name": "Journal Entry Project",
"project_template_name": "Test Project Template",
"start_date": "2020-01-01",
}
)
project_name = project.name
else:
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
@@ -325,17 +390,17 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
expected_values = {
"_Test Cash - _TC": {
"project": project_name
},
"_Test Bank - _TC": {
"project": project_name
}
"_Test Cash - _TC": {"project": project_name},
"_Test Bank - _TC": {"project": project_name},
}
gl_entries = frappe.db.sql("""select account, project, debit, credit
gl_entries = frappe.db.sql(
"""select account, project, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", jv.name, as_dict=1)
order by account asc""",
jv.name,
as_dict=1,
)
self.assertTrue(gl_entries)
@@ -345,9 +410,12 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
jv = make_journal_entry(
"_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center=cost_center, save=False
)
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
@@ -360,7 +428,18 @@ class TestJournalEntry(unittest.TestCase):
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
def make_journal_entry(
account1,
account2,
amount,
cost_center=None,
posting_date=None,
exchange_rate=1,
save=True,
submit=False,
project=None,
):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
@@ -369,23 +448,27 @@ def make_journal_entry(account1, account2, amount, cost_center=None, posting_dat
jv.company = "_Test Company"
jv.user_remark = "test"
jv.multi_currency = 1
jv.set("accounts", [
{
"account": account1,
"cost_center": cost_center,
"project": project,
"debit_in_account_currency": amount if amount > 0 else 0,
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate
}, {
"account": account2,
"cost_center": cost_center,
"project": project,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate
}
])
jv.set(
"accounts",
[
{
"account": account1,
"cost_center": cost_center,
"project": project,
"debit_in_account_currency": amount if amount > 0 else 0,
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate,
},
{
"account": account2,
"cost_center": cost_center,
"project": project,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate,
},
],
)
if save or submit:
jv.insert()
@@ -394,4 +477,5 @@ def make_journal_entry(account1, account2, amount, cost_center=None, posting_dat
return jv
test_records = frappe.get_test_records('Journal Entry')
test_records = frappe.get_test_records("Journal Entry")

View File

@@ -9,6 +9,7 @@ from frappe.model.document import Document
class JournalEntryTemplate(Document):
pass
@frappe.whitelist()
def get_naming_series():
return frappe.get_meta("Journal Entry").get_field("naming_series").options

View File

@@ -8,6 +8,7 @@ from frappe.utils import today
exclude_from_linked_with = True
class LoyaltyPointEntry(Document):
pass
@@ -16,18 +17,28 @@ def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=No
if not expiry_date:
expiry_date = today()
return frappe.db.sql('''
return frappe.db.sql(
"""
select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice
from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s
and expiry_date>=%s and loyalty_points>0 and company=%s
order by expiry_date
''', (customer, loyalty_program, expiry_date, company), as_dict=1)
""",
(customer, loyalty_program, expiry_date, company),
as_dict=1,
)
def get_redemption_details(customer, loyalty_program, company):
return frappe._dict(frappe.db.sql('''
return frappe._dict(
frappe.db.sql(
"""
select redeem_against, sum(loyalty_points)
from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and loyalty_points<0 and company=%s
group by redeem_against
''', (customer, loyalty_program, company)))
""",
(customer, loyalty_program, company),
)
)

View File

@@ -12,39 +12,61 @@ class LoyaltyProgram(Document):
pass
def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None, include_expired_entry=False):
def get_loyalty_details(
customer, loyalty_program, expiry_date=None, company=None, include_expired_entry=False
):
if not expiry_date:
expiry_date = today()
condition = ''
condition = ""
if company:
condition = " and company=%s " % frappe.db.escape(company)
if not include_expired_entry:
condition += " and expiry_date>='%s' " % expiry_date
loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points,
loyalty_point_details = frappe.db.sql(
"""select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
group by customer'''.format(condition=condition),
(customer, loyalty_program, expiry_date), as_dict=1)
group by customer""".format(
condition=condition
),
(customer, loyalty_program, expiry_date),
as_dict=1,
)
if loyalty_point_details:
return loyalty_point_details[0]
else:
return {"loyalty_points": 0, "total_spent": 0}
@frappe.whitelist()
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, \
silent=False, include_expired_entry=False, current_transaction_amount=0):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
key=lambda rule:rule.min_spent, reverse=True)
@frappe.whitelist()
def get_loyalty_program_details_with_points(
customer,
loyalty_program=None,
expiry_date=None,
company=None,
silent=False,
include_expired_entry=False,
current_transaction_amount=0,
):
lp_details = get_loyalty_program_details(
customer, loyalty_program, company=company, silent=silent
)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)
)
tier_spent_level = sorted(
[d.as_dict() for d in loyalty_program.collection_rules],
key=lambda rule: rule.min_spent,
reverse=True,
)
for i, d in enumerate(tier_spent_level):
if i==0 or (lp_details.total_spent+current_transaction_amount) <= d.min_spent:
if i == 0 or (lp_details.total_spent + current_transaction_amount) <= d.min_spent:
lp_details.tier_name = d.tier_name
lp_details.collection_factor = d.collection_factor
else:
@@ -52,8 +74,16 @@ def get_loyalty_program_details_with_points(customer, loyalty_program=None, expi
return lp_details
@frappe.whitelist()
def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False):
def get_loyalty_program_details(
customer,
loyalty_program=None,
expiry_date=None,
company=None,
silent=False,
include_expired_entry=False,
):
lp_details = frappe._dict()
if not loyalty_program:
@@ -72,6 +102,7 @@ def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None
lp_details.update(loyalty_program.as_dict())
return lp_details
@frappe.whitelist()
def get_redeemption_factor(loyalty_program=None, customer=None):
customer_loyalty_program = None
@@ -98,13 +129,16 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
else:
loyalty_program = frappe.db.get_value("Customer", ref_doc.customer, ["loyalty_program"])
if loyalty_program and frappe.db.get_value("Loyalty Program", loyalty_program, ["company"]) !=\
ref_doc.company:
if (
loyalty_program
and frappe.db.get_value("Loyalty Program", loyalty_program, ["company"]) != ref_doc.company
):
frappe.throw(_("The Loyalty Program isn't valid for the selected company"))
if loyalty_program and points_to_redeem:
loyalty_program_details = get_loyalty_program_details_with_points(ref_doc.customer, loyalty_program,
posting_date, ref_doc.company)
loyalty_program_details = get_loyalty_program_details_with_points(
ref_doc.customer, loyalty_program, posting_date, ref_doc.company
)
if points_to_redeem > loyalty_program_details.loyalty_points:
frappe.throw(_("You don't have enought Loyalty Points to redeem"))

View File

@@ -1,11 +1,5 @@
def get_data():
return {
'fieldname': 'loyalty_program',
'transactions': [
{
'items': ['Sales Invoice', 'Customer']
}
]
"fieldname": "loyalty_program",
"transactions": [{"items": ["Sales Invoice", "Customer"]}],
}

View File

@@ -19,19 +19,28 @@ class TestLoyaltyProgram(unittest.TestCase):
create_records()
def test_loyalty_points_earned_single_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
si_original.submit()
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
earned_points = get_points_earned(si_original)
lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
lpe = frappe.get_doc(
"Loyalty Point Entry",
{
"invoice_type": "Sales Invoice",
"invoice": si_original.name,
"customer": si_original.customer,
},
)
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
@@ -43,21 +52,31 @@ class TestLoyaltyProgram(unittest.TestCase):
earned_after_redemption = get_points_earned(si_redeem)
lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
lpe_redeem = frappe.get_doc(
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "redeem_against": lpe.name},
)
lpe_earn = frappe.get_doc(
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "name": ["!=", lpe_redeem.name]},
)
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
self.assertEqual(lpe_redeem.loyalty_points, (-1 * earned_points))
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty"
)
# assign multiple tier program to the customer
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc('Loyalty Program', {'loyalty_program_name': 'Test Multiple Loyalty'}).name
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc(
"Loyalty Program", {"loyalty_program_name": "Test Multiple Loyalty"}
).name
customer.save()
# create a new sales invoice
@@ -67,10 +86,17 @@ class TestLoyaltyProgram(unittest.TestCase):
earned_points = get_points_earned(si_original)
lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
lpe = frappe.get_doc(
"Loyalty Point Entry",
{
"invoice_type": "Sales Invoice",
"invoice": si_original.name,
"customer": si_original.customer,
},
)
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
@@ -80,14 +106,20 @@ class TestLoyaltyProgram(unittest.TestCase):
si_redeem.insert()
si_redeem.submit()
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
earned_after_redemption = get_points_earned(si_redeem)
lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
lpe_redeem = frappe.get_doc(
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "redeem_against": lpe.name},
)
lpe_earn = frappe.get_doc(
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si_redeem.name, "name": ["!=", lpe_redeem.name]},
)
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
self.assertEqual(lpe_redeem.loyalty_points, (-1 * earned_points))
self.assertEqual(lpe_earn.loyalty_program_tier, customer.loyalty_program_tier)
# cancel and delete
@@ -95,23 +127,30 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel()
def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points'''
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
"""cancelling the sales invoice should cancel the earned points"""
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
si.submit()
lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si.name, 'customer': si.customer})
lpe = frappe.get_doc(
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
)
self.assertEqual(True, not (lpe is None))
# cancelling sales invoice
si.cancel()
lpe = frappe.db.exists('Loyalty Point Entry', lpe.name)
lpe = frappe.db.exists("Loyalty Point Entry", lpe.name)
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
@@ -119,7 +158,14 @@ class TestLoyaltyProgram(unittest.TestCase):
si_original.submit()
earned_points = get_points_earned(si_original)
lpe_original = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
lpe_original = frappe.get_doc(
"Loyalty Point Entry",
{
"invoice_type": "Sales Invoice",
"invoice": si_original.name,
"customer": si_original.customer,
},
)
self.assertEqual(lpe_original.loyalty_points, earned_points)
# create sales invoice return
@@ -131,10 +177,17 @@ class TestLoyaltyProgram(unittest.TestCase):
si_return.submit()
# fetch original invoice again as its status would have been updated
si_original = frappe.get_doc('Sales Invoice', lpe_original.invoice)
si_original = frappe.get_doc("Sales Invoice", lpe_original.invoice)
earned_points = get_points_earned(si_original)
lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
lpe_after_return = frappe.get_doc(
"Loyalty Point Entry",
{
"invoice_type": "Sales Invoice",
"invoice": si_original.name,
"customer": si_original.customer,
},
)
self.assertEqual(lpe_after_return.loyalty_points, earned_points)
self.assertEqual(True, (lpe_original.loyalty_points > lpe_after_return.loyalty_points))
@@ -143,144 +196,164 @@ class TestLoyaltyProgram(unittest.TestCase):
try:
d.cancel()
except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.get_doc("Sales Invoice", d.name).cancel()
def test_loyalty_points_for_dashboard(self):
doc = frappe.get_doc('Customer', 'Test Loyalty Customer')
doc = frappe.get_doc("Customer", "Test Loyalty Customer")
company_wise_info = get_dashboard_info("Customer", doc.name, doc.loyalty_program)
for d in company_wise_info:
self.assertTrue(d.get("loyalty_points"))
def get_points_earned(self):
def get_returned_amount():
returned_amount = frappe.db.sql("""
returned_amount = frappe.db.sql(
"""
select sum(grand_total)
from `tabSales Invoice`
where docstatus=1 and is_return=1 and ifnull(return_against, '')=%s
""", self.name)
""",
self.name,
)
return abs(flt(returned_amount[0][0])) if returned_amount else 0
lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
loyalty_program=self.loyalty_program, expiry_date=self.posting_date, include_expired_entry=True)
if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
lp_details = get_loyalty_program_details_with_points(
self.customer,
company=self.company,
loyalty_program=self.loyalty_program,
expiry_date=self.posting_date,
include_expired_entry=True,
)
if (
lp_details
and getdate(lp_details.from_date) <= getdate(self.posting_date)
and (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date))
):
returned_amount = get_returned_amount()
eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount
points_earned = cint(eligible_amount/lp_details.collection_factor)
points_earned = cint(eligible_amount / lp_details.collection_factor)
return points_earned or 0
def create_sales_invoice_record(qty=1):
# return sales invoice doc object
return frappe.get_doc({
"doctype": "Sales Invoice",
"customer": frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"}).name,
"company": '_Test Company',
"due_date": today(),
"posting_date": today(),
"currency": "INR",
"taxes_and_charges": "",
"debit_to": "Debtors - _TC",
"taxes": [],
"items": [{
'doctype': 'Sales Invoice Item',
'item_code': frappe.get_doc('Item', {'item_name': 'Loyal Item'}).name,
'qty': qty,
"rate": 10000,
'income_account': 'Sales - _TC',
'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC'
}]
})
return frappe.get_doc(
{
"doctype": "Sales Invoice",
"customer": frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"}).name,
"company": "_Test Company",
"due_date": today(),
"posting_date": today(),
"currency": "INR",
"taxes_and_charges": "",
"debit_to": "Debtors - _TC",
"taxes": [],
"items": [
{
"doctype": "Sales Invoice Item",
"item_code": frappe.get_doc("Item", {"item_name": "Loyal Item"}).name,
"qty": qty,
"rate": 10000,
"income_account": "Sales - _TC",
"cost_center": "Main - _TC",
"expense_account": "Cost of Goods Sold - _TC",
}
],
}
)
def create_records():
# create a new loyalty Account
if not frappe.db.exists("Account", "Loyalty - _TC"):
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
frappe.get_doc(
{
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}
).insert()
# create a new loyalty program Single tier
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 1000
}]
}).insert()
if not frappe.db.exists("Loyalty Program", "Test Single Loyalty"):
frappe.get_doc(
{
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 1000}],
}
).insert()
# create a new customer
if not frappe.db.exists("Customer","Test Loyalty Customer"):
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
if not frappe.db.exists("Customer", "Test Loyalty Customer"):
frappe.get_doc(
{
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory",
}
).insert()
# create a new loyalty program Multiple tier
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
if not frappe.db.exists("Loyalty Program", "Test Multiple Loyalty"):
frappe.get_doc(
{
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
],
}
).insert()
# create an item
if not frappe.db.exists("Item", "Loyal Item"):
frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
frappe.get_doc(
{
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}
).insert()
# create item price
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": "Loyal Item",
"price_list_rate": 10000
}).insert()
if not frappe.db.exists(
"Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}
):
frappe.get_doc(
{
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": "Loyal Item",
"price_list_rate": 10000,
}
).insert()

View File

@@ -19,23 +19,35 @@ class ModeofPayment(Document):
for entry in self.accounts:
accounts_list.append(entry.company)
if len(accounts_list)!= len(set(accounts_list)):
if len(accounts_list) != len(set(accounts_list)):
frappe.throw(_("Same Company is entered more than once"))
def validate_accounts(self):
for entry in self.accounts:
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
frappe.throw(_("Account {0} does not match with Company {1} in Mode of Account: {2}")
.format(entry.default_account, entry.company, self.name))
frappe.throw(
_("Account {0} does not match with Company {1} in Mode of Account: {2}").format(
entry.default_account, entry.company, self.name
)
)
def validate_pos_mode_of_payment(self):
if not self.enabled:
pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip
WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name))
pos_profiles = frappe.db.sql(
"""SELECT sip.parent FROM `tabSales Invoice Payment` sip
WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""",
(self.name),
)
pos_profiles = list(map(lambda x: x[0], pos_profiles))
if pos_profiles:
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
message = (
"POS Profile "
+ frappe.bold(", ".join(pos_profiles))
+ " contains \
Mode of Payment "
+ frappe.bold(str(self.name))
+ ". Please remove them to disable this mode."
)
frappe.throw(_(message), title="Not Allowed")

View File

@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Mode of Payment')
class TestModeofPayment(unittest.TestCase):
pass

View File

@@ -11,13 +11,25 @@ from frappe.utils import add_months, flt
class MonthlyDistribution(Document):
@frappe.whitelist()
def get_months(self):
month_list = ['January','February','March','April','May','June','July','August','September',
'October','November','December']
idx =1
month_list = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]
idx = 1
for m in month_list:
mnth = self.append('percentages')
mnth = self.append("percentages")
mnth.month = m
mnth.percentage_allocation = 100.0/12
mnth.percentage_allocation = 100.0 / 12
mnth.idx = idx
idx += 1
@@ -25,18 +37,15 @@ class MonthlyDistribution(Document):
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
if flt(total, 2) != 100.0:
frappe.throw(_("Percentage Allocation should be equal to 100%") + \
" ({0}%)".format(str(flt(total, 2))))
frappe.throw(
_("Percentage Allocation should be equal to 100%") + " ({0}%)".format(str(flt(total, 2)))
)
def get_periodwise_distribution_data(distribution_id, period_list, periodicity):
doc = frappe.get_doc('Monthly Distribution', distribution_id)
doc = frappe.get_doc("Monthly Distribution", distribution_id)
months_to_add = {
"Yearly": 12,
"Half-Yearly": 6,
"Quarterly": 3,
"Monthly": 1
}[periodicity]
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_dict = {}
@@ -45,6 +54,7 @@ def get_periodwise_distribution_data(distribution_id, period_list, periodicity):
return period_dict
def get_percentage(doc, start_date, period):
percentage = 0
months = [start_date.strftime("%B").title()]

View File

@@ -1,22 +1,16 @@
from frappe import _
def get_data():
return {
'fieldname': 'monthly_distribution',
'non_standard_fieldnames': {
'Sales Person': 'distribution_id',
'Territory': 'distribution_id',
'Sales Partner': 'distribution_id',
"fieldname": "monthly_distribution",
"non_standard_fieldnames": {
"Sales Person": "distribution_id",
"Territory": "distribution_id",
"Sales Partner": "distribution_id",
},
'transactions': [
{
'label': _('Target Details'),
'items': ['Sales Person', 'Territory', 'Sales Partner']
},
{
'items': ['Budget']
}
]
"transactions": [
{"label": _("Target Details"), "items": ["Sales Person", "Territory", "Sales Partner"]},
{"items": ["Budget"]},
],
}

View File

@@ -6,7 +6,8 @@ import unittest
import frappe
test_records = frappe.get_test_records('Monthly Distribution')
test_records = frappe.get_test_records("Monthly Distribution")
class TestMonthlyDistribution(unittest.TestCase):
pass

View File

@@ -20,9 +20,9 @@ class OpeningInvoiceCreationTool(Document):
def onload(self):
"""Load the Opening Invoice summary"""
summary, max_count = self.get_opening_invoice_summary()
self.set_onload('opening_invoices_summary', summary)
self.set_onload('max_count', max_count)
self.set_onload('temporary_opening_account', get_temporary_opening_account(self.company))
self.set_onload("opening_invoices_summary", summary)
self.set_onload("max_count", max_count)
self.set_onload("temporary_opening_account", get_temporary_opening_account(self.company))
def get_opening_invoice_summary(self):
def prepare_invoice_summary(doctype, invoices):
@@ -32,10 +32,7 @@ class OpeningInvoiceCreationTool(Document):
for invoice in invoices:
company = invoice.pop("company")
_summary = invoices_summary.get(company, {})
_summary.update({
"currency": company_wise_currency.get(company),
doctype: invoice
})
_summary.update({"currency": company_wise_currency.get(company), doctype: invoice})
invoices_summary.update({company: _summary})
if invoice.paid_amount:
@@ -44,17 +41,21 @@ class OpeningInvoiceCreationTool(Document):
outstanding_amount.append(invoice.outstanding_amount)
if paid_amount or outstanding_amount:
max_count.update({
doctype: {
"max_paid": max(paid_amount) if paid_amount else 0.0,
"max_due": max(outstanding_amount) if outstanding_amount else 0.0
max_count.update(
{
doctype: {
"max_paid": max(paid_amount) if paid_amount else 0.0,
"max_due": max(outstanding_amount) if outstanding_amount else 0.0,
}
}
})
)
invoices_summary = {}
max_count = {}
fields = [
"company", "count(name) as total_invoices", "sum(outstanding_amount) as outstanding_amount"
"company",
"count(name) as total_invoices",
"sum(outstanding_amount) as outstanding_amount",
]
companies = frappe.get_all("Company", fields=["name as company", "default_currency as currency"])
if not companies:
@@ -62,8 +63,9 @@ class OpeningInvoiceCreationTool(Document):
company_wise_currency = {row.company: row.currency for row in companies}
for doctype in ["Sales Invoice", "Purchase Invoice"]:
invoices = frappe.get_all(doctype, filters=dict(is_opening="Yes", docstatus=1),
fields=fields, group_by="company")
invoices = frappe.get_all(
doctype, filters=dict(is_opening="Yes", docstatus=1), fields=fields, group_by="company"
)
prepare_invoice_summary(doctype, invoices)
return invoices_summary, max_count
@@ -74,7 +76,9 @@ class OpeningInvoiceCreationTool(Document):
def set_missing_values(self, row):
row.qty = row.qty or 1.0
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(
self.company
)
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
row.item_name = row.item_name or _("Opening Invoice Item")
row.posting_date = row.posting_date or nowdate()
@@ -85,7 +89,11 @@ class OpeningInvoiceCreationTool(Document):
if self.create_missing_party:
self.add_party(row.party_type, row.party)
else:
frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
frappe.throw(
_("Row #{}: {} {} does not exist.").format(
row.idx, frappe.bold(row.party_type), frappe.bold(row.party)
)
)
mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
@@ -100,12 +108,19 @@ class OpeningInvoiceCreationTool(Document):
self.set_missing_values(row)
self.validate_mandatory_invoice_fields(row)
invoice = self.get_invoice_dict(row)
company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
company_details = (
frappe.get_cached_value(
"Company", self.company, ["default_currency", "default_letter_head"], as_dict=1
)
or {}
)
if company_details:
invoice.update({
"currency": company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head")
})
invoice.update(
{
"currency": company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head"),
}
)
invoices.append(invoice)
return invoices
@@ -127,47 +142,61 @@ class OpeningInvoiceCreationTool(Document):
def get_invoice_dict(self, row=None):
def get_item_dict():
cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company, "cost_center")
cost_center = row.get("cost_center") or frappe.get_cached_value(
"Company", self.company, "cost_center"
)
if not cost_center:
frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
frappe.throw(
_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
)
income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
income_expense_account_field = (
"income_account" if row.party_type == "Customer" else "expense_account"
)
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
rate = flt(row.outstanding_amount) / flt(row.qty)
return frappe._dict({
"uom": default_uom,
"rate": rate or 0.0,
"qty": row.qty,
"conversion_factor": 1.0,
"item_name": row.item_name or "Opening Invoice Item",
"description": row.item_name or "Opening Invoice Item",
income_expense_account_field: row.temporary_opening_account,
"cost_center": cost_center
})
item_dict = frappe._dict(
{
"uom": default_uom,
"rate": rate or 0.0,
"qty": row.qty,
"conversion_factor": 1.0,
"item_name": row.item_name or "Opening Invoice Item",
"description": row.item_name or "Opening Invoice Item",
income_expense_account_field: row.temporary_opening_account,
"cost_center": cost_center,
}
)
for dimension in get_accounting_dimensions():
item_dict.update({dimension: row.get(dimension)})
return item_dict
item = get_item_dict()
invoice = frappe._dict({
"items": [item],
"is_opening": "Yes",
"set_posting_time": 1,
"company": self.company,
"cost_center": self.cost_center,
"due_date": row.due_date,
"posting_date": row.posting_date,
frappe.scrub(row.party_type): row.party,
"is_pos": 0,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0,
"invoice_number": row.invoice_number
})
invoice = frappe._dict(
{
"items": [item],
"is_opening": "Yes",
"set_posting_time": 1,
"company": self.company,
"cost_center": self.cost_center,
"due_date": row.due_date,
"posting_date": row.posting_date,
frappe.scrub(row.party_type): row.party,
"is_pos": 0,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0, # important: https://github.com/frappe/erpnext/pull/23559
"invoice_number": row.invoice_number,
"disable_rounded_total": 1,
}
)
accounting_dimension = get_accounting_dimensions()
for dimension in accounting_dimension:
invoice.update({
dimension: item.get(dimension)
})
invoice.update({dimension: self.get(dimension) or item.get(dimension)})
return invoice
@@ -193,9 +222,10 @@ class OpeningInvoiceCreationTool(Document):
event="opening_invoice_creation",
job_name=self.name,
invoices=invoices,
now=frappe.conf.developer_mode or frappe.flags.in_test
now=frappe.conf.developer_mode or frappe.flags.in_test,
)
def start_import(invoices):
errors = 0
names = []
@@ -214,35 +244,43 @@ def start_import(invoices):
except Exception:
errors += 1
frappe.db.rollback()
message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
message = "\n".join(
["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]
)
frappe.log_error(title="Error while creating Opening Invoice", message=message)
frappe.db.commit()
if errors:
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
.format(errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
frappe.msgprint(
_("You had {} errors while creating opening invoices. Check {} for more details").format(
errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"
),
indicator="red",
title=_("Error Occured"),
)
return names
def publish(index, total, doctype):
if total < 5: return
if total < 5:
return
frappe.publish_realtime(
"opening_invoice_creation_progress",
dict(
title=_("Opening Invoice Creation In Progress"),
message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
message=_("Creating {} out of {} {}").format(index + 1, total, doctype),
user=frappe.session.user,
count=index+1,
total=total
))
count=index + 1,
total=total,
),
)
@frappe.whitelist()
def get_temporary_opening_account(company=None):
if not company:
return
accounts = frappe.get_all("Account", filters={
'company': company,
'account_type': 'Temporary'
})
accounts = frappe.get_all("Account", filters={"company": company, "account_type": "Temporary"})
if not accounts:
frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts"))

View File

@@ -1,51 +1,64 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account,
)
test_dependencies = ["Customer", "Supplier"]
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def setUp(self):
class TestOpeningInvoiceCreationTool(FrappeTestCase):
@classmethod
def setUpClass(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
create_dimension()
return super().setUpClass()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
def make_invoices(
self,
invoice_type="Sales",
company=None,
party_1=None,
party_2=None,
invoice_number=None,
department=None,
):
doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
args = get_opening_invoice_creation_dict(
invoice_type=invoice_type,
company=company,
party_1=party_1,
party_2=party_2,
invoice_number=invoice_number,
department=department,
)
doc.update(args)
return doc.make_invoices()
def test_opening_sales_invoice_creation(self):
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
try:
invoices = self.make_invoices(company="_Test Opening Invoice Company")
invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
"keys": ["customer", "outstanding_amount", "status"],
0: ["_Test Customer", 300, "Overdue"],
1: ["_Test Customer 1", 250, "Overdue"],
}
self.check_expected_values(invoices, expected_value)
self.assertEqual(len(invoices), 2)
expected_value = {
"keys": ["customer", "outstanding_amount", "status"],
0: ["_Test Customer", 300, "Overdue"],
1: ["_Test Customer 1", 250, "Overdue"],
}
self.check_expected_values(invoices, expected_value)
si = frappe.get_doc("Sales Invoice", invoices[0])
si = frappe.get_doc("Sales Invoice", invoices[0])
# Check if update stock is not enabled
self.assertEqual(si.update_stock, 0)
finally:
property_setter.delete()
clear_doctype_cache("Sales Invoice")
# Check if update stock is not enabled
self.assertEqual(si.update_stock, 0)
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -70,15 +83,30 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
old_default_receivable_account = frappe.db.get_value(
"Company", company, "default_receivable_account"
)
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
"is_group": 1, "company": "_Test Opening Invoice Company"})
cc = frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "_Test Opening Invoice Company",
"is_group": 1,
"company": "_Test Opening Invoice Company",
}
)
cc.insert(ignore_mandatory=True)
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
cc2 = frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "Main",
"is_group": 0,
"company": "_Test Opening Invoice Company",
"parent_cost_center": cc.name,
}
)
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
@@ -86,60 +114,86 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# Check if missing debit account error raised
error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
error_log = frappe.db.exists(
"Error Log",
{"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]},
)
self.assertTrue(error_log)
# teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
frappe.db.set_value(
"Company", company, "default_receivable_account", old_default_receivable_account
)
def test_renaming_of_invoice_using_invoice_number_field(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11")
self.make_invoices(
company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11"
)
sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name")
sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name")
sales_inv1 = frappe.get_all("Sales Invoice", filters={"customer": "Customer A"})[0].get("name")
sales_inv2 = frappe.get_all("Sales Invoice", filters={"customer": "Customer B"})[0].get("name")
self.assertEqual(sales_inv1, "TEST-NEW-INV-11")
#teardown
# teardown
for inv in [sales_inv1, sales_inv2]:
doc = frappe.get_doc('Sales Invoice', inv)
doc = frappe.get_doc("Sales Invoice", inv)
doc.cancel()
def test_opening_invoice_with_accounting_dimension(self):
invoices = self.make_invoices(
invoice_type="Sales", company="_Test Opening Invoice Company", department="Sales - _TOIC"
)
expected_value = {
"keys": ["customer", "outstanding_amount", "status", "department"],
0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"],
}
self.check_expected_values(invoices, expected_value, invoice_type="Sales")
def tearDown(self):
disable_dimension()
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
invoice_dict = frappe._dict({
"company": company,
"invoice_type": args.get("invoice_type", "Sales"),
"invoices": [
{
"qty": 1.0,
"outstanding_amount": 300,
"party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": args.get("invoice_number")
},
{
"qty": 2.0,
"outstanding_amount": 250,
"party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": None
}
]
})
invoice_dict = frappe._dict(
{
"company": company,
"invoice_type": args.get("invoice_type", "Sales"),
"invoices": [
{
"qty": 1.0,
"outstanding_amount": 300,
"party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": args.get("invoice_number"),
},
{
"qty": 2.0,
"outstanding_amount": 250,
"party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": None,
},
],
}
)
invoice_dict.update(args)
return invoice_dict
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
@@ -152,15 +206,18 @@ def make_company():
company.insert()
return company
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_type": "Company",
"territory": "All Territories"
})
customer = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_type": "Company",
"territory": "All Territories",
}
)
if not frappe.db.exists("Customer", customer_name):
customer.insert(ignore_permissions=True)
return customer.name

View File

@@ -3,6 +3,7 @@
"creation": "2014-08-29 16:02:39.740505",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"account"
@@ -11,6 +12,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
@@ -27,7 +29,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-07 18:13:08.833822",
"modified": "2022-04-04 12:31:02.994197",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Party Account",
@@ -35,5 +37,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -8,32 +8,43 @@ from frappe.model.document import Document
class PartyLink(Document):
def validate(self):
if self.primary_role not in ['Customer', 'Supplier']:
frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."),
title=_("Invalid Primary Role"))
existing_party_link = frappe.get_all('Party Link', {
'primary_party': self.secondary_party
}, pluck="primary_role")
if self.primary_role not in ["Customer", "Supplier"]:
frappe.throw(
_(
"Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."
),
title=_("Invalid Primary Role"),
)
existing_party_link = frappe.get_all(
"Party Link", {"primary_party": self.secondary_party}, pluck="primary_role"
)
if existing_party_link:
frappe.throw(_('{} {} is already linked with another {}')
.format(self.secondary_role, self.secondary_party, existing_party_link[0]))
existing_party_link = frappe.get_all('Party Link', {
'secondary_party': self.primary_party
}, pluck="primary_role")
frappe.throw(
_("{} {} is already linked with another {}").format(
self.secondary_role, self.secondary_party, existing_party_link[0]
)
)
existing_party_link = frappe.get_all(
"Party Link", {"secondary_party": self.primary_party}, pluck="primary_role"
)
if existing_party_link:
frappe.throw(_('{} {} is already linked with another {}')
.format(self.primary_role, self.primary_party, existing_party_link[0]))
frappe.throw(
_("{} {} is already linked with another {}").format(
self.primary_role, self.primary_party, existing_party_link[0]
)
)
@frappe.whitelist()
def create_party_link(primary_role, primary_party, secondary_party):
party_link = frappe.new_doc('Party Link')
party_link = frappe.new_doc("Party Link")
party_link.primary_role = primary_role
party_link.primary_party = primary_party
party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
party_link.secondary_role = "Customer" if primary_role == "Supplier" else "Supplier"
party_link.secondary_party = secondary_party
party_link.save(ignore_permissions=True)
return party_link

View File

@@ -196,8 +196,14 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_from_account_currency != company_currency));
if (frm.doc.payment_type == "Pay") {
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_to_account_currency != company_currency));
} else {
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_from_account_currency != company_currency));
}
frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency
@@ -220,10 +226,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.total_allocated_amount > party_amount)));
frm.toggle_display("set_exchange_gain_loss",
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
((frm.doc.paid_from_account_currency != company_currency ||
frm.doc.paid_to_account_currency != company_currency) &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
frm.refresh_fields();
},
@@ -232,7 +235,8 @@ frappe.ui.form.on('Payment Entry', {
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax",
"base_total_taxes_and_charges"], company_currency);
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@@ -341,6 +345,8 @@ frappe.ui.form.on('Payment Entry', {
}
frm.set_party_account_based_on_party = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
return frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details",
args: {
@@ -374,7 +380,11 @@ frappe.ui.form.on('Payment Entry', {
if (r.message.bank_account) {
frm.set_value("bank_account", r.message.bank_account);
}
}
},
() => frm.events.set_current_exchange_rate(frm, "source_exchange_rate",
frm.doc.paid_from_account_currency, company_currency),
() => frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
frm.doc.paid_to_account_currency, company_currency)
]);
}
}
@@ -478,14 +488,14 @@ frappe.ui.form.on('Payment Entry', {
},
paid_from_account_currency: function(frm) {
if(!frm.doc.paid_from_account_currency) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if(!frm.doc.paid_from_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from){
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
@@ -505,8 +515,8 @@ frappe.ui.form.on('Payment Entry', {
},
paid_to_account_currency: function(frm) {
if(!frm.doc.paid_to_account_currency) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if(!frm.doc.paid_to_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
frm.doc.paid_to_account_currency, company_currency);

View File

@@ -66,7 +66,9 @@
"tax_withholding_category",
"section_break_56",
"taxes",
"section_break_60",
"base_total_taxes_and_charges",
"column_break_61",
"total_taxes_and_charges",
"deductions_or_loss_section",
"deductions",
@@ -715,12 +717,21 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Paid To Account Type"
},
{
"fieldname": "column_break_61",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_60",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-11-24 18:58:24.919764",
"modified": "2022-02-23 20:08:39.559814",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -763,6 +774,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

File diff suppressed because it is too large Load Diff

View File

@@ -32,10 +32,9 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
["Debtors - _TC", 0, 1000, so.name],
["_Test Cash - _TC", 1000.0, 0, None]
])
expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -48,9 +47,9 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(so_advance_paid, 0)
def test_payment_entry_for_blocked_supplier_invoice(self):
supplier = frappe.get_doc('Supplier', '_Test Supplier')
supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.hold_type = 'Invoices'
supplier.hold_type = "Invoices"
supplier.save()
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
@@ -59,32 +58,40 @@ class TestPaymentEntry(unittest.TestCase):
supplier.save()
def test_payment_entry_for_blocked_supplier_payments(self):
supplier = frappe.get_doc('Supplier', '_Test Supplier')
supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.hold_type = 'Payments'
supplier.hold_type = "Payments"
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
bank_account="_Test Bank - _TC")
frappe.ValidationError,
get_payment_entry,
dt="Purchase Invoice",
dn=pi.name,
bank_account="_Test Bank - _TC",
)
supplier.on_hold = 0
supplier.save()
def test_payment_entry_for_blocked_supplier_payments_today_date(self):
supplier = frappe.get_doc('Supplier', '_Test Supplier')
supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.hold_type = 'Payments'
supplier.hold_type = "Payments"
supplier.release_date = nowdate()
supplier.save()
pi = make_purchase_invoice()
self.assertRaises(
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
bank_account="_Test Bank - _TC")
frappe.ValidationError,
get_payment_entry,
dt="Purchase Invoice",
dn=pi.name,
bank_account="_Test Bank - _TC",
)
supplier.on_hold = 0
supplier.save()
@@ -93,15 +100,15 @@ class TestPaymentEntry(unittest.TestCase):
# this test is meant to fail only if something fails in the try block
with self.assertRaises(Exception):
try:
supplier = frappe.get_doc('Supplier', '_Test Supplier')
supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
supplier.hold_type = 'Payments'
supplier.release_date = '2018-03-01'
supplier.hold_type = "Payments"
supplier.release_date = "2018-03-01"
supplier.save()
pi = make_purchase_invoice()
get_payment_entry('Purchase Invoice', pi.name, bank_account="_Test Bank - _TC")
get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
supplier.on_hold = 0
supplier.save()
@@ -111,8 +118,12 @@ class TestPaymentEntry(unittest.TestCase):
raise Exception
def test_payment_entry_against_si_usd_to_usd(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -120,10 +131,13 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
["_Test Receivable USD - _TC", 0, 5000, si.name],
["_Test Bank USD - _TC", 5000.0, 0, None]
])
expected_gle = dict(
(d[0], d)
for d in [
["_Test Receivable USD - _TC", 0, 5000, si.name],
["_Test Bank USD - _TC", 5000.0, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -136,8 +150,12 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 100)
def test_payment_entry_against_pi(self):
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
pi = make_purchase_invoice(
supplier="_Test Supplier USD",
debit_to="_Test Payable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -145,20 +163,26 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
["_Test Payable USD - _TC", 12500, 0, pi.name],
["_Test Bank USD - _TC", 0, 12500, None]
])
expected_gle = dict(
(d[0], d)
for d in [
["_Test Payable USD - _TC", 12500, 0, pi.name],
["_Test Bank USD - _TC", 0, 12500, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
def test_payment_against_sales_invoice_to_check_status(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
@@ -167,28 +191,35 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
outstanding_amount, status = frappe.db.get_value(
"Sales Invoice", si.name, ["outstanding_amount", "status"]
)
self.assertEqual(flt(outstanding_amount), 0)
self.assertEqual(status, 'Paid')
self.assertEqual(status, "Paid")
pe.cancel()
outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
outstanding_amount, status = frappe.db.get_value(
"Sales Invoice", si.name, ["outstanding_amount", "status"]
)
self.assertEqual(flt(outstanding_amount), 100)
self.assertEqual(status, 'Unpaid')
self.assertEqual(status, "Unpaid")
def test_payment_entry_against_payment_terms(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template()
si.payment_terms_template = 'Test Receivable Template'
si.payment_terms_template = "Test Receivable Template"
si.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18
})
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18,
},
)
si.save()
si.submit()
@@ -197,25 +228,28 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
si.load_from_db()
self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
self.assertEqual(pe.references[0].payment_term, "Basic Amount Receivable")
self.assertEqual(pe.references[1].payment_term, "Tax Receivable")
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_entry_against_payment_terms_with_discount(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template_with_discount()
si.payment_terms_template = 'Test Discount Template'
si.payment_terms_template = "Test Discount Template"
frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
si.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18
})
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18,
},
)
si.save()
si.submit()
@@ -224,16 +258,19 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
si.load_from_db()
self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
self.assertEqual(si.payment_schedule[0].outstanding, 0)
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
def test_payment_against_purchase_invoice_to_check_status(self):
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
pi = make_purchase_invoice(
supplier="_Test Supplier USD",
debit_to="_Test Payable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
@@ -242,21 +279,27 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
outstanding_amount, status = frappe.db.get_value(
"Purchase Invoice", pi.name, ["outstanding_amount", "status"]
)
self.assertEqual(flt(outstanding_amount), 0)
self.assertEqual(status, 'Paid')
self.assertEqual(status, "Paid")
pe.cancel()
outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
outstanding_amount, status = frappe.db.get_value(
"Purchase Invoice", pi.name, ["outstanding_amount", "status"]
)
self.assertEqual(flt(outstanding_amount), 250)
self.assertEqual(status, 'Unpaid')
self.assertEqual(status, "Unpaid")
def test_payment_entry_against_ec(self):
payable = frappe.get_cached_value('Company', "_Test Company", 'default_payable_account')
payable = frappe.get_cached_value("Company", "_Test Company", "default_payable_account")
ec = make_expense_claim(payable, 300, 300, "_Test Company", "Travel Expenses - _TC")
pe = get_payment_entry("Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300)
pe = get_payment_entry(
"Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300
)
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 1
@@ -264,68 +307,87 @@ class TestPaymentEntry(unittest.TestCase):
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
[payable, 300, 0, ec.name],
["_Test Bank USD - _TC", 0, 300, None]
])
expected_gle = dict(
(d[0], d) for d in [[payable, 300, 0, ec.name], ["_Test Bank USD - _TC", 0, 300, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")) - \
flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
outstanding_amount = flt(
frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")
) - flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_si_usd_to_inr(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
bank_account="_Test Bank - _TC", bank_amount=900)
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry(
"Sales Invoice", si.name, party_amount=20, bank_account="_Test Bank - _TC", bank_amount=900
)
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
self.assertEqual(pe.difference_amount, 100)
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 100
})
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 100,
},
)
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
["_Test Receivable USD - _TC", 0, 1000, si.name],
["_Test Bank - _TC", 900, 0, None],
["_Test Exchange Gain/Loss - _TC", 100.0, 0, None],
])
expected_gle = dict(
(d[0], d)
for d in [
["_Test Receivable USD - _TC", 0, 1000, si.name],
["_Test Bank - _TC", 900, 0, None],
["_Test Exchange Gain/Loss - _TC", 100.0, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 80)
def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency (self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50, do_not_save=1)
def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_save=1,
)
si.plc_conversion_rate = 50
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
bank_account="_Test Bank USD - _TC", bank_amount=900)
pe = get_payment_entry(
"Sales Invoice", si.name, party_amount=20, bank_account="_Test Bank USD - _TC", bank_amount=900
)
pe.source_exchange_rate = 45.263
pe.target_exchange_rate = 45.263
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 94.80
})
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 94.80,
},
)
pe.save()
@@ -359,8 +421,7 @@ class TestPaymentEntry(unittest.TestCase):
pe.set_amounts()
self.assertEqual(
pe.source_exchange_rate, 65.1,
"{0} is not equal to {1}".format(pe.source_exchange_rate, 65.1)
pe.source_exchange_rate, 65.1, "{0} is not equal to {1}".format(pe.source_exchange_rate, 65.1)
)
def test_internal_transfer_usd_to_inr(self):
@@ -382,20 +443,26 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(pe.difference_amount, 500)
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 500
})
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 500,
},
)
pe.insert()
pe.submit()
expected_gle = dict((d[0], d) for d in [
["_Test Bank USD - _TC", 0, 5000, None],
["_Test Bank - _TC", 4500, 0, None],
["_Test Exchange Gain/Loss - _TC", 500.0, 0, None],
])
expected_gle = dict(
(d[0], d)
for d in [
["_Test Bank USD - _TC", 0, 5000, None],
["_Test Bank - _TC", 4500, 0, None],
["_Test Exchange Gain/Loss - _TC", 500.0, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -435,10 +502,9 @@ class TestPaymentEntry(unittest.TestCase):
pe3.insert()
pe3.submit()
expected_gle = dict((d[0], d) for d in [
["Debtors - _TC", 100, 0, si1.name],
["_Test Cash - _TC", 0, 100, None]
])
expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 100, 0, si1.name], ["_Test Cash - _TC", 0, 100, None]]
)
self.validate_gl_entries(pe3.name, expected_gle)
@@ -462,12 +528,16 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(expected_gle[gle.account][3], gle.against_voucher)
def get_gle(self, voucher_no):
return frappe.db.sql("""select account, debit, credit, against_voucher
return frappe.db.sql(
"""select account, debit, credit, against_voucher
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", voucher_no, as_dict=1)
order by account asc""",
voucher_no,
as_dict=1,
)
def test_payment_entry_write_off_difference(self):
si = create_sales_invoice()
si = create_sales_invoice()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
@@ -477,11 +547,10 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(pe.unallocated_amount, 10)
pe.received_amount = pe.paid_amount = 95
pe.append("deductions", {
"account": "_Test Write Off - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 5
})
pe.append(
"deductions",
{"account": "_Test Write Off - _TC", "cost_center": "_Test Cost Center - _TC", "amount": 5},
)
pe.save()
self.assertEqual(pe.unallocated_amount, 0)
@@ -489,27 +558,37 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_gle = dict((d[0], d) for d in [
["Debtors - _TC", 0, 100, si.name],
["_Test Cash - _TC", 95, 0, None],
["_Test Write Off - _TC", 5, 0, None]
])
expected_gle = dict(
(d[0], d)
for d in [
["Debtors - _TC", 0, 100, si.name],
["_Test Cash - _TC", 95, 0, None],
["_Test Write Off - _TC", 5, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
def test_payment_entry_exchange_gain_loss(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 55
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": -500
})
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": -500,
},
)
pe.save()
self.assertEqual(pe.unallocated_amount, 0)
@@ -517,11 +596,14 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_gle = dict((d[0], d) for d in [
["_Test Receivable USD - _TC", 0, 5000, si.name],
["_Test Bank USD - _TC", 5500, 0, None],
["_Test Exchange Gain/Loss - _TC", 0, 500, None],
])
expected_gle = dict(
(d[0], d)
for d in [
["_Test Receivable USD - _TC", 0, 5000, si.name],
["_Test Bank USD - _TC", 5500, 0, None],
["_Test Exchange Gain/Loss - _TC", 0, 500, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -530,10 +612,11 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_against_sales_invoice_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, si.cost_center)
@@ -546,18 +629,18 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_values = {
"_Test Bank - _TC": {
"cost_center": cost_center
},
"Debtors - _TC": {
"cost_center": cost_center
}
"_Test Bank - _TC": {"cost_center": cost_center},
"Debtors - _TC": {"cost_center": cost_center},
}
gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
gl_entries = frappe.db.sql(
"""select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", pe.name, as_dict=1)
order by account asc""",
pe.name,
as_dict=1,
)
self.assertTrue(gl_entries)
@@ -566,10 +649,13 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_against_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
pi = make_purchase_invoice_against_cost_center(
cost_center=cost_center, credit_to="Creditors - _TC"
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, pi.cost_center)
@@ -582,18 +668,18 @@ class TestPaymentEntry(unittest.TestCase):
pe.submit()
expected_values = {
"_Test Bank - _TC": {
"cost_center": cost_center
},
"Creditors - _TC": {
"cost_center": cost_center
}
"_Test Bank - _TC": {"cost_center": cost_center},
"Creditors - _TC": {"cost_center": cost_center},
}
gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
gl_entries = frappe.db.sql(
"""select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", pe.name, as_dict=1)
order by account asc""",
pe.name,
as_dict=1,
)
self.assertTrue(gl_entries)
@@ -603,13 +689,16 @@ class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_account_and_party_balance_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=si.cost_center)
party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
party_balance = get_balance_on(
party_type="Customer", party=si.customer, cost_center=si.cost_center
)
party_account_balance = get_balance_on(si.debit_to, cost_center=si.cost_center)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
@@ -633,56 +722,110 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(flt(expected_party_balance), party_balance)
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
def test_multi_currency_payment_entry_with_taxes(self):
payment_entry = create_payment_entry(
party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True
)
payment_entry.append(
"taxes",
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "Actual",
"tax_amount": 10,
"add_deduct_tax": "Add",
"description": "Test",
},
)
payment_entry.save()
self.assertEqual(payment_entry.base_total_taxes_and_charges, 10)
self.assertEqual(
flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
payment_entry.company = args.get("company") or "_Test Company"
payment_entry.payment_type = args.get("payment_type") or "Pay"
payment_entry.party_type = args.get("party_type") or "Supplier"
payment_entry.party = args.get("party") or "_Test Supplier"
payment_entry.paid_from = args.get("paid_from") or "_Test Bank - _TC"
payment_entry.paid_to = args.get("paid_to") or "Creditors - _TC"
payment_entry.paid_amount = args.get("paid_amount") or 1000
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
payment_entry.set_exchange_rate()
payment_entry.received_amount = payment_entry.paid_amount / payment_entry.target_exchange_rate
payment_entry.reference_no = "Test001"
payment_entry.reference_date = nowdate()
if args.get("save"):
payment_entry.save()
if args.get("submit"):
payment_entry.submit()
return payment_entry
def create_payment_terms_template():
create_payment_term('Basic Amount Receivable')
create_payment_term('Tax Receivable')
create_payment_term("Basic Amount Receivable")
create_payment_term("Tax Receivable")
if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
payment_term_template = frappe.get_doc({
'doctype': 'Payment Terms Template',
'template_name': 'Test Receivable Template',
'allocate_payment_based_on_payment_terms': 1,
'terms': [{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Basic Amount Receivable',
'invoice_portion': 84.746,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 1
},
if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"):
payment_term_template = frappe.get_doc(
{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Tax Receivable',
'invoice_portion': 15.254,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 2
}]
}).insert()
"doctype": "Payment Terms Template",
"template_name": "Test Receivable Template",
"allocate_payment_based_on_payment_terms": 1,
"terms": [
{
"doctype": "Payment Terms Template Detail",
"payment_term": "Basic Amount Receivable",
"invoice_portion": 84.746,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 1,
},
{
"doctype": "Payment Terms Template Detail",
"payment_term": "Tax Receivable",
"invoice_portion": 15.254,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 2,
},
],
}
).insert()
def create_payment_terms_template_with_discount():
create_payment_term('30 Credit Days with 10% Discount')
create_payment_term("30 Credit Days with 10% Discount")
if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
payment_term_template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "Test Discount Template",
"allocate_payment_based_on_payment_terms": 1,
"terms": [
{
"doctype": "Payment Terms Template Detail",
"payment_term": "30 Credit Days with 10% Discount",
"invoice_portion": 100,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 2,
"discount": 10,
"discount_validity_based_on": "Day(s) after invoice date",
"discount_validity": 1,
}
],
}
).insert()
if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
payment_term_template = frappe.get_doc({
'doctype': 'Payment Terms Template',
'template_name': 'Test Discount Template',
'allocate_payment_based_on_payment_terms': 1,
'terms': [{
'doctype': 'Payment Terms Template Detail',
'payment_term': '30 Credit Days with 10% Discount',
'invoice_portion': 100,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 2,
'discount': 10,
'discount_validity_based_on': 'Day(s) after invoice date',
'discount_validity': 1
}]
}).insert()
def create_payment_term(name):
if not frappe.db.exists('Payment Term', name):
frappe.get_doc({
'doctype': 'Payment Term',
'payment_term_name': name
}).insert()
if not frappe.db.exists("Payment Term", name):
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()

View File

@@ -18,10 +18,13 @@ class PaymentGatewayAccount(Document):
def update_default_payment_gateway(self):
if self.is_default:
frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """)
frappe.db.sql(
"""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """
)
def set_as_default_if_not_set(self):
if not frappe.db.get_value("Payment Gateway Account",
{"is_default": 1, "name": ("!=", self.name)}, "name"):
if not frappe.db.get_value(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
):
self.is_default = 1

View File

@@ -1,17 +1,6 @@
def get_data():
return {
'fieldname': 'payment_gateway_account',
'non_standard_fieldnames': {
'Subscription Plan': 'payment_gateway'
},
'transactions': [
{
'items': ['Payment Request']
},
{
'items': ['Subscription Plan']
}
]
"fieldname": "payment_gateway_account",
"non_standard_fieldnames": {"Subscription Plan": "payment_gateway"},
"transactions": [{"items": ["Payment Request"]}, {"items": ["Subscription Plan"]}],
}

View File

@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Payment Gateway Account')
class TestPaymentGatewayAccount(unittest.TestCase):
pass

View File

@@ -12,7 +12,6 @@ frappe.ui.form.on('Payment Order', {
});
frm.set_df_property('references', 'cannot_add_rows', true);
frm.set_df_property('references', 'cannot_delete_rows', true);
},
refresh: function(frm) {
if (frm.doc.docstatus == 0) {

View File

@@ -18,9 +18,9 @@ class PaymentOrder(Document):
self.update_payment_status(cancel=True)
def update_payment_status(self, cancel=False):
status = 'Payment Ordered'
status = "Payment Ordered"
if cancel:
status = 'Initiated'
status = "Initiated"
if self.payment_order_type == "Payment Request":
ref_field = "status"
@@ -32,67 +32,67 @@ class PaymentOrder(Document):
for d in self.references:
frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference`
return frappe.db.sql(
""" select mode_of_payment from `tabPayment Order Reference`
where parent = %(parent)s and mode_of_payment like %(txt)s
limit %(start)s, %(page_len)s""", {
'parent': filters.get("parent"),
'start': start,
'page_len': page_len,
'txt': "%%%s%%" % txt
})
limit %(start)s, %(page_len)s""",
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select supplier from `tabPayment Order Reference`
return frappe.db.sql(
""" select supplier from `tabPayment Order Reference`
where parent = %(parent)s and supplier like %(txt)s and
(payment_reference is null or payment_reference='')
limit %(start)s, %(page_len)s""", {
'parent': filters.get("parent"),
'start': start,
'page_len': page_len,
'txt': "%%%s%%" % txt
})
limit %(start)s, %(page_len)s""",
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
)
@frappe.whitelist()
def make_payment_records(name, supplier, mode_of_payment=None):
doc = frappe.get_doc('Payment Order', name)
doc = frappe.get_doc("Payment Order", name)
make_journal_entry(doc, supplier, mode_of_payment)
def make_journal_entry(doc, supplier, mode_of_payment=None):
je = frappe.new_doc('Journal Entry')
je = frappe.new_doc("Journal Entry")
je.payment_order = doc.name
je.posting_date = nowdate()
mode_of_payment_type = frappe._dict(frappe.get_all('Mode of Payment',
fields = ["name", "type"], as_list=1))
mode_of_payment_type = frappe._dict(
frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1)
)
je.voucher_type = 'Bank Entry'
if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == 'Cash':
je.voucher_type = "Bank Entry"
if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == "Cash":
je.voucher_type = "Cash Entry"
paid_amt = 0
party_account = get_party_account('Supplier', supplier, doc.company)
party_account = get_party_account("Supplier", supplier, doc.company)
for d in doc.references:
if (d.supplier == supplier
and (not mode_of_payment or mode_of_payment == d.mode_of_payment)):
je.append('accounts', {
'account': party_account,
'debit_in_account_currency': d.amount,
'party_type': 'Supplier',
'party': supplier,
'reference_type': d.reference_doctype,
'reference_name': d.reference_name
})
if d.supplier == supplier and (not mode_of_payment or mode_of_payment == d.mode_of_payment):
je.append(
"accounts",
{
"account": party_account,
"debit_in_account_currency": d.amount,
"party_type": "Supplier",
"party": supplier,
"reference_type": d.reference_doctype,
"reference_name": d.reference_name,
},
)
paid_amt += d.amount
je.append('accounts', {
'account': doc.account,
'credit_in_account_currency': paid_amt
})
je.append("accounts", {"account": doc.account, "credit_in_account_currency": paid_amt})
je.flags.ignore_mandatory = True
je.save()

View File

@@ -1,11 +1,5 @@
def get_data():
return {
'fieldname': 'payment_order',
'transactions': [
{
'items': ['Payment Entry', 'Journal Entry']
}
]
"fieldname": "payment_order",
"transactions": [{"items": ["Payment Entry", "Journal Entry"]}],
}

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