Compare commits

..

314 Commits

Author SHA1 Message Date
Frappe PR Bot
7ba029b140 chore(release): Bumped to Version 13.40.2
## [13.40.2](https://github.com/frappe/erpnext/compare/v13.40.1...v13.40.2) (2022-10-19)

### Bug Fixes

* `Brand Defaults` filters ([b1c70af](b1c70af4db))
* don't try to update youtube data if disabled in settings (backport [#32588](https://github.com/frappe/erpnext/issues/32588)) ([#32590](https://github.com/frappe/erpnext/issues/32590)) ([7e7122b](7e7122b668))
* Ignore linked purchase invoice on cancel ([eec770a](eec770a4a8))
* Party account for multi-order invoices ([0ae2a4f](0ae2a4f1c4))
* pricing rule item code UOM apply & conversions (backport [#32566](https://github.com/frappe/erpnext/issues/32566)) ([#32636](https://github.com/frappe/erpnext/issues/32636)) ([b8d97b8](b8d97b82b8))
2022-10-19 03:57:46 +00:00
Deepesh Garg
cf74d24400 Merge pull request #32634 from frappe/version-13-hotfix
chore: release v13
2022-10-19 09:24:40 +05:30
mergify[bot]
b8d97b82b8 fix: pricing rule item code UOM apply & conversions (backport #32566) (#32636)
* fix: pricing rule for non stock UOM and conversions

* fix: pricing rule for non stock UOM and conversions

(cherry picked from commit 96b4211ea1)

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

* chore: resolve conflicts

Co-authored-by: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2022-10-18 23:31:31 +05:30
Deepesh Garg
56c4c3186b Merge pull request #32624 from frappe/mergify/bp/version-13-hotfix/pr-32622
fix: Ignore linked purchase invoice on cancel (backport #32622)
2022-10-18 14:49:36 +05:30
Deepesh Garg
015a221d8d chore: Resolve conflicts 2022-10-18 09:11:58 +05:30
Deepesh Garg
eec770a4a8 fix: Ignore linked purchase invoice on cancel
(cherry picked from commit faadf78332)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2022-10-17 14:09:12 +00:00
Deepesh Garg
44f5ccc9dc Merge pull request #32605 from frappe/mergify/bp/version-13-hotfix/pr-32594
fix: Party account for multi-order invoices (backport #32594)
2022-10-15 18:25:25 +05:30
Deepesh Garg
0ae2a4f1c4 fix: Party account for multi-order invoices
(cherry picked from commit fd49503ba2)
2022-10-15 11:29:21 +00:00
mergify[bot]
5fdaddad86 chore: drop dead code (backport #32595) (#32597)
chore: drop dead code (#32595)

[skip ci]

(cherry picked from commit 50e9698932)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-10-13 15:27:09 +05:30
Sagar Sharma
03ccddc1cc Merge pull request #32587 from frappe/mergify/bp/version-13-hotfix/pr-32576
fix: `Brand Defaults` filters (backport #32576)
2022-10-13 14:42:29 +05:30
Sagar Sharma
e00ebcd9e5 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32576 2022-10-13 14:38:26 +05:30
mergify[bot]
7e7122b668 fix: don't try to update youtube data if disabled in settings (backport #32588) (#32590)
* fix: don't try to update youtube data if disabled in settings (#32588)

fix: cast value from db

[skip ci]

(cherry picked from commit e543dca6a0)

* chore: qualified path

not imported 

[skip ci]

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-10-13 11:57:08 +05:30
Sagar Sharma
b1c70af4db fix: Brand Defaults filters
(cherry picked from commit 7da32c7db3)
2022-10-12 13:53:56 +00:00
Frappe PR Bot
1609129e1e chore(release): Bumped to Version 13.40.1
## [13.40.1](https://github.com/frappe/erpnext/compare/v13.40.0...v13.40.1) (2022-10-12)

### Bug Fixes

* conflct in test purchase receipt ([6ae2f90](6ae2f90683))
* conflict ([cb7aef5](cb7aef505d))
* consider outgoingrate while valuation rate calculate ([6f43133](6f43133c04))
* consider sales rate as incoming rate for transit warehouse in purchase flow ([f72602e](f72602ebf3))
* Hanlde rounding loss for internal transfer ([dd26ef9](dd26ef96e0))
* Incoming rate precision fixes for intra company transfer ([227ce5f](227ce5f8a2))
* incorrect import ([8238b89](8238b89907))
* linter issue ([aa0552d](aa0552d788))
* test case ([acd64ba](acd64ba7c1))
* value error on pos submit ([4383980](4383980bcc))
2022-10-12 12:34:41 +00:00
Deepesh Garg
9b02455c09 Merge pull request #32585 from frappe/version-13-hotfix
chore: version 13 release
2022-10-12 18:03:01 +05:30
Deepesh Garg
ff3bfb060a Merge pull request #32584 from frappe/mergify/bp/version-13-hotfix/pr-32557
fix: Value error on validation of POS invoices with Serial Nos (backport #32557)
2022-10-12 17:17:01 +05:30
rohitwaghchaure
63e087d31e Merge pull request #32580 from frappe/mergify/bp/version-13-hotfix/pr-32563
fix: consider sales rate as incoming rate for transit warehouse in purchase flow (backport #32563)
2022-10-12 17:08:33 +05:30
Deepesh Garg
b62a397397 Merge pull request #32581 from frappe/mergify/bp/version-13-hotfix/pr-32272
fix: Incoming rate precision fixes for intra company transfer (backport #32272)
2022-10-12 17:04:52 +05:30
ruthra kumar
48efcc82b6 test: value error on serial no validation on pos
(cherry picked from commit 9e2bd10d03)
2022-10-12 11:12:09 +00:00
ruthra kumar
4383980bcc fix: value error on pos submit
(cherry picked from commit 4b908ebcd6)
2022-10-12 11:12:08 +00:00
rohitwaghchaure
aa0552d788 fix: linter issue 2022-10-12 16:33:53 +05:30
Deepesh Garg
3b5889ef85 chore: resolve conflicts 2022-10-12 16:30:23 +05:30
rohitwaghchaure
8238b89907 fix: incorrect import 2022-10-12 16:28:31 +05:30
rohitwaghchaure
6ae2f90683 fix: conflct in test purchase receipt 2022-10-12 16:26:52 +05:30
rohitwaghchaure
cb7aef505d fix: conflict 2022-10-12 16:24:50 +05:30
Deepesh Garg
4a8c42d62b chore: check only for inter-company transfers
(cherry picked from commit 9aa5e20ef7)
2022-10-12 10:54:15 +00:00
Deepesh Garg
c67bdcf3c6 chore: fix precision condition
(cherry picked from commit 49601558c6)
2022-10-12 10:54:14 +00:00
Deepesh Garg
7a9e7b66ac chore: Use proper accounts
(cherry picked from commit 1c05c004cd)
2022-10-12 10:54:13 +00:00
Deepesh Garg
151b9d4d7b chore: Increase precision for other doc fields
(cherry picked from commit c8d2181498)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
2022-10-12 10:54:13 +00:00
Deepesh Garg
b966f10711 chore: GL Entries for SLE diff
(cherry picked from commit df2a0e265b)
2022-10-12 10:54:11 +00:00
Deepesh Garg
0b48f13873 test: Internal tranfer precision loss test
(cherry picked from commit dc20b21fb5)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2022-10-12 10:54:10 +00:00
Deepesh Garg
b531a38efe chore: Increase incoming_rate field precision to 6
(cherry picked from commit b31c3bd35d)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
2022-10-12 10:54:08 +00:00
Deepesh Garg
dd26ef96e0 fix: Hanlde rounding loss for internal transfer
(cherry picked from commit 6e47fd54a0)
2022-10-12 10:54:07 +00:00
Deepesh Garg
227ce5f8a2 fix: Incoming rate precision fixes for intra company transfer
(cherry picked from commit 083309c056)
2022-10-12 10:54:06 +00:00
Rohit Waghchaure
acd64ba7c1 fix: test case
(cherry picked from commit 98bf8e1304)
2022-10-12 10:51:22 +00:00
Rohit Waghchaure
6f43133c04 fix: consider outgoingrate while valuation rate calculate
(cherry picked from commit 3266e54e33)
2022-10-12 10:51:22 +00:00
Rohit Waghchaure
f72602ebf3 fix: consider sales rate as incoming rate for transit warehouse in purchase flow
(cherry picked from commit 683a47f7a1)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
#	erpnext/stock/stock_ledger.py
2022-10-12 10:51:21 +00:00
Frappe PR Bot
49c9c68e14 chore(release): Bumped to Version 13.40.0
# [13.40.0](https://github.com/frappe/erpnext/compare/v13.39.5...v13.40.0) (2022-10-12)

### Bug Fixes

* Do not add tax withheld vouchers post tax withheding in one document ([e1c41b9](e1c41b9195))
* future attendance marking ([cb4fbd5](cb4fbd5432))
* make readings status mandatory in Quality Inspection ([257a2a3](257a2a3d71))
* mark attendance issue with joining date ([6f8d620](6f8d62088e))
* mark attendance issue with relieving date ([a9546dd](a9546dd01f))
* PO cancel post advance payment cancel against PO ([41599cf](41599cf29f))
* set Quality Inspection status based on readings status ([d67b44f](d67b44fcec))
* Tax withholding related fixes ([32a9575](32a9575f07))
* **test:** `test_rejected_qi_validation` ([2763d06](2763d06307))
* TooManyWritesError during reposting of stock ([476175b](476175b307))

### Features

* cst ux improvement ([552c595](552c5951bd))
* **JE:** trigger account field when fetched from template ([baa4fec](baa4fec611)), closes [#32409](https://github.com/frappe/erpnext/issues/32409)
2022-10-12 07:21:47 +00:00
Ankush Menat
9ce765b268 Merge pull request #32561 from frappe/version-13-hotfix
chore: release v13
2022-10-12 12:49:45 +05:30
Ankush Menat
002ae8ae13 ci: disable test orchestrator v13 (#32574) 2022-10-12 12:13:51 +05:30
Deepesh Garg
558dc57b94 Merge pull request #32539 from frappe/mergify/bp/version-13-hotfix/pr-32536
fix: PO cancel post advance payment cancel against PO (backport #32536)
2022-10-11 13:58:24 +05:30
Deepesh Garg
cd942d36a8 chore: resolve conflicts 2022-10-11 10:16:31 +05:30
Sagar Sharma
3257533ee3 Merge pull request #32544 from frappe/mergify/bp/version-13-hotfix/pr-32497
chore: set `Quality Inspection` status based on readings status (backport #32497)
2022-10-10 12:10:29 +05:30
Sagar Sharma
2763d06307 fix(test): test_rejected_qi_validation
(cherry picked from commit 4992e4a2b8)
2022-10-10 05:21:09 +00:00
Sagar Sharma
a0772ea8d7 test: add test cases for Quality Inspection status
(cherry picked from commit fcc1272d42)
2022-10-10 05:21:08 +00:00
Sagar Sharma
d67b44fcec fix: set Quality Inspection status based on readings status
(cherry picked from commit 2657ece2cd)
2022-10-10 05:21:08 +00:00
Sagar Sharma
257a2a3d71 fix: make readings status mandatory in Quality Inspection
(cherry picked from commit d7c3b7633a)
2022-10-10 05:21:07 +00:00
Sagar Sharma
e6abbd1c83 chore: add Manual Inspection field in Quality Inspection DocType
(cherry picked from commit 39707757a6)
2022-10-10 05:21:06 +00:00
Deepesh Garg
41599cf29f fix: PO cancel post advance payment cancel against PO
(cherry picked from commit d806e32030)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py
2022-10-09 13:05:46 +00:00
Frappe PR Bot
c66ebcdf9a chore(release): Bumped to Version 13.39.5
## [13.39.5](https://github.com/frappe/erpnext/compare/v13.39.4...v13.39.5) (2022-10-07)

### Bug Fixes

* Do not add tax withheld vouchers post tax withheding in one document ([be404b7](be404b77a1))
* Tax withholding related fixes ([6e9b52d](6e9b52d268))
2022-10-07 13:08:29 +00:00
Deepesh Garg
4623b986a3 Merge pull request #32526 from frappe/mergify/bp/version-13/pr-32523
fix: Tax withholding related fixes (backport #32522) (backport #32523)
2022-10-07 18:36:57 +05:30
Deepesh Garg
611dd5c073 chore: resolve conflicts
(cherry picked from commit 2bf76f64bd)
2022-10-07 12:39:14 +00:00
Deepesh Garg
be404b77a1 fix: Do not add tax withheld vouchers post tax withheding in one document
(cherry picked from commit 781d160c68)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
(cherry picked from commit e1c41b9195)
2022-10-07 12:39:13 +00:00
Deepesh Garg
6e9b52d268 fix: Tax withholding related fixes
(cherry picked from commit abf5b6be3e)
(cherry picked from commit 32a9575f07)
2022-10-07 12:39:13 +00:00
Deepesh Garg
997990c69f Merge pull request #32523 from frappe/mergify/bp/version-13-hotfix/pr-32522
fix: Tax withholding related fixes (backport #32522)
2022-10-07 18:08:31 +05:30
Deepesh Garg
2bf76f64bd chore: resolve conflicts 2022-10-07 17:33:03 +05:30
Deepesh Garg
e1c41b9195 fix: Do not add tax withheld vouchers post tax withheding in one document
(cherry picked from commit 781d160c68)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
2022-10-07 11:03:16 +00:00
Deepesh Garg
32a9575f07 fix: Tax withholding related fixes
(cherry picked from commit abf5b6be3e)
2022-10-07 11:03:15 +00:00
Deepesh Garg
e031e095dd Merge pull request #32521 from FHenry/13_fr_translation
chore: fr translation (backport #32385)
2022-10-07 08:48:43 +05:30
Florian HENRY
39ce3756c8 chore: fr translation 2022-10-06 22:39:40 +02:00
Deepesh Garg
f2515d9c9c Merge pull request #32449 from rtdany10/cst-ux-improvement
feat(ux): improved course scheduling tool
2022-10-05 18:44:05 +05:30
Deepesh Garg
27fe9c9179 Merge pull request #32426 from AnandBaburajan/fix_mark_attendance_joining_date_and_future
fix: mark attendance issue with joining and relieving date, and fix future attendance marking
2022-10-05 18:34:31 +05:30
Deepesh Garg
503183cb0b Merge pull request #32502 from frappe/mergify/bp/version-13-hotfix/pr-32499
fix: TooManyWritesError during reposting of stock (backport #32499)
2022-10-05 17:29:25 +05:30
Deepesh Garg
f1ea5de022 Merge pull request #32504 from frappe/mergify/bp/version-13-hotfix/pr-32478
feat(JE): trigger account field when fetched from template (backport #32478)
2022-10-05 17:29:05 +05:30
Dany Robert
baa4fec611 feat(JE): trigger account field when fetched from template
Closes #32409

(cherry picked from commit c35adcf5a1)
2022-10-05 10:38:21 +00:00
Rohit Waghchaure
476175b307 fix: TooManyWritesError during reposting of stock
(cherry picked from commit aaabba9b1e)
2022-10-05 10:35:55 +00:00
Frappe PR Bot
de584535e9 chore(release): Bumped to Version 13.39.4
## [13.39.4](https://github.com/frappe/erpnext/compare/v13.39.3...v13.39.4) (2022-10-04)

### Bug Fixes

* asset requiring maintenance sold status ([14ab9d9](14ab9d9158))
* conflict ([0f8e34e](0f8e34e972))
* Disbursement Account in patch to update old loans ([b4a511c](b4a511cbb4))
* e-Way bill JSON for Intra-state internal transfers ([b7fbf75](b7fbf75e10))
* fetch swift_number in payment_request ([6074570](60745705a8))
* Incorrect TCS amount deducted in Sales Invoice ([32456bf](32456bff30))
* Item details fetching on making transaction from item dashboard ([1aed76f](1aed76f778))
* linter issue ([4757a65](4757a65863))
* not able to return sold expired batches ([2fd4485](2fd4485f2f))
* payment reconciliation tool consider cost_center for JV ([9f98891](9f988910b5))
* payment request make bank field Link instead of Read Only ([299175e](299175e6fe))
* pick list picked-qty for batch item ([96a6db0](96a6db0422))
* SEZ Without Payment of Tax don't add tax rows ([a13eecc](a13eecc961))
* show `Make Purchase Invoice` button based on permission ([7124328](7124328640))
* typo in sales_register's filter mode_of_payment (backport [#32371](https://github.com/frappe/erpnext/issues/32371)) ([#32447](https://github.com/frappe/erpnext/issues/32447)) ([cdc8297](cdc8297083))
* update with new Frappe color. fix [#32455](https://github.com/frappe/erpnext/issues/32455) ([#32456](https://github.com/frappe/erpnext/issues/32456)) ([ad43b18](ad43b18ace))

### Reverts

* Revert "fix: fetch swift_number in payment_request" ([8251b84](8251b843ec))
2022-10-04 12:14:27 +00:00
Deepesh Garg
d95d234aa4 Merge pull request #32494 from frappe/version-13-hotfix
chore: release v13
2022-10-04 17:41:06 +05:30
rohitwaghchaure
3648745e5e Merge pull request #32470 from frappe/mergify/bp/version-13-hotfix/pr-32466
fix: not able to return sold expired batches (backport #32466)
2022-10-04 07:35:10 +05:30
Deepesh Garg
8e1e6a194b Merge pull request #32467 from frappe/mergify/bp/version-13-hotfix/pr-32394
fix: fetch swift number in payment request from bank doctype (backport #32394)
2022-10-03 21:54:05 +05:30
Ankush Menat
663e9b403f chore: codeowners 2022-10-03 16:27:32 +05:30
Ankush Menat
3219b7c766 chore: codeowners 2022-10-03 16:27:13 +05:30
Sagar Sharma
3a6e4c8da7 Merge pull request #32474 from frappe/mergify/bp/version-13-hotfix/pr-32472
fix: pick list picked-qty for batch item (backport #32472)
2022-10-03 15:24:02 +05:30
Sagar Sharma
96a6db0422 fix: pick list picked-qty for batch item
(cherry picked from commit ba02209f1d)
2022-10-03 09:17:03 +00:00
rohitwaghchaure
4757a65863 fix: linter issue 2022-10-03 13:27:38 +05:30
rohitwaghchaure
0f8e34e972 fix: conflict 2022-10-03 13:18:58 +05:30
Rohit Waghchaure
2fd4485f2f fix: not able to return sold expired batches
(cherry picked from commit 0b1727cf79)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2022-10-03 07:46:13 +00:00
Maharshi Patel
299175e6fe fix: payment request make bank field Link instead of Read Only
(cherry picked from commit dc8d49260c)
2022-10-03 07:26:06 +00:00
Maharshi Patel
8251b843ec Revert "fix: fetch swift_number in payment_request"
This reverts commit f42a8e4e03.

(cherry picked from commit 9245d3b5cd)
2022-10-03 07:26:05 +00:00
Maharshi Patel
60745705a8 fix: fetch swift_number in payment_request
There isn't direct link between payment_request and bank so swift_number wasn't fetched using Fetch form. I fixed it by fetching swift_number on_change of bank_account.

(cherry picked from commit f42a8e4e03)
2022-10-03 07:26:04 +00:00
Deepesh Garg
caedc9f6ad Merge pull request #32462 from frappe/mergify/bp/version-13-hotfix/pr-32456
fix: update with new Frappe color. fix #32455 (backport #32456)
2022-10-03 09:15:21 +05:30
Deepesh Garg
fbeb86b9c0 Merge pull request #32453 from deepeshgarg007/intra_state_transfer_eway_bill_v13
fix: e-Way bill JSON for Intra-state internal transfers
2022-10-03 09:07:26 +05:30
Muvuk
ad43b18ace fix: update with new Frappe color. fix #32455 (#32456)
* Update with new Frappe color.

* refactor: use css variables

Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
(cherry picked from commit 73e5a7d671)
2022-10-02 17:02:25 +00:00
Frappe PR Bot
34284216cd chore(release): Bumped to Version 13.39.3
## [13.39.3](https://github.com/frappe/erpnext/compare/v13.39.2...v13.39.3) (2022-10-02)

### Bug Fixes

* asset requiring maintenance sold status ([a668d4e](a668d4e789))
2022-10-02 15:39:08 +00:00
Deepesh Garg
5c6c9812c0 Merge pull request #32458 from frappe/mergify/bp/version-13/pr-32413
fix: status of assets with maintenance_required changing back to 'Partially Depreciated' some time after being sold [v13] (backport #32413)
2022-10-02 21:07:00 +05:30
anandbaburajan
4afd2b7470 chore: refactor by just using a filter
(cherry picked from commit 6b21deedae)
2022-10-02 12:14:41 +00:00
anandbaburajan
bc21ce412b chore: refactor by creating is_sold
(cherry picked from commit 430a4c98a0)
2022-10-02 12:14:41 +00:00
anandbaburajan
a668d4e789 fix: asset requiring maintenance sold status
(cherry picked from commit 14ab9d9158)
2022-10-02 12:14:40 +00:00
Deepesh Garg
9f8d3e43ea Merge pull request #32413 from AnandBaburajan/fix_asset_sold_status_v13
fix: status of assets with maintenance_required changing back to 'Partially Depreciated' some time after being sold [v13]
2022-10-02 17:43:09 +05:30
Deepesh Garg
9dbb2bf512 Merge pull request #32369 from maharshivpatel/fix-taxes-sez-without-payment-of-tax
fix: SEZ Without Payment of Tax don't add tax rows
2022-10-01 16:13:11 +05:30
Deepesh Garg
8c52c71299 Merge pull request #32344 from maharshivpatel/fix-payment-reconciliation-jv-cost-center
fix: consider cost center for journal entry in payment reconciliation tool
2022-10-01 16:12:30 +05:30
Deepesh Garg
c4c52c1cd3 chore: Linting Issues 2022-10-01 16:06:31 +05:30
Deepesh Garg
b7fbf75e10 fix: e-Way bill JSON for Intra-state internal transfers 2022-10-01 15:58:31 +05:30
Dany Robert
6dabfbedf1 Merge branch 'version-13-hotfix' into cst-ux-improvement 2022-10-01 14:19:31 +05:30
Dany Robert
68c592377b chore: pre-commit 2022-09-30 23:41:01 -07:00
Deepesh Garg
d69d2217f6 Merge pull request #32431 from frappe/mergify/bp/version-13-hotfix/pr-32402
fix: Item details fetching on making transaction from item dashboard (backport #32402)
2022-10-01 12:05:26 +05:30
mergify[bot]
cdc8297083 fix: typo in sales_register's filter mode_of_payment (backport #32371) (#32447)
fix: typo in sales_register's filter mode_of_payment (#32371)
2022-10-01 12:04:40 +05:30
Dany Robert
f24f71f387 Merge branch 'version-13-hotfix' into cst-ux-improvement 2022-10-01 12:03:05 +05:30
Dany Robert
552c5951bd feat: cst ux improvement 2022-09-30 23:25:55 -07:00
Deepesh Garg
562025eb45 Merge branch 'version-13-hotfix' into fix-payment-reconciliation-jv-cost-center 2022-10-01 11:10:21 +05:30
Deepesh Garg
f0a37f545a chore: resolve conflicts 2022-10-01 11:02:40 +05:30
Deepesh Garg
001afb96ba Merge pull request #32435 from frappe/mergify/bp/version-13-hotfix/pr-32412
fix: Incorrect TCS amount deducted in Sales Invoice (backport #32412)
2022-10-01 11:01:05 +05:30
Deepesh Garg
61d3370527 chore: Remove print statements
(cherry picked from commit bff3cd9068)
2022-09-30 10:36:20 +00:00
Deepesh Garg
32456bff30 fix: Incorrect TCS amount deducted in Sales Invoice
(cherry picked from commit 08443c6421)
2022-09-30 10:36:20 +00:00
Deepesh Garg
1aed76f778 fix: Item details fetching on making transaction from item dashboard
(cherry picked from commit 0439e41a44)

# Conflicts:
#	erpnext/stock/doctype/item/item.js
2022-09-30 10:27:36 +00:00
anandbaburajan
308c400c6a chore: make linter happy 2022-09-30 15:33:34 +05:30
anandbaburajan
a9546dd01f fix: mark attendance issue with relieving date 2022-09-30 15:27:03 +05:30
anandbaburajan
cb4fbd5432 fix: future attendance marking 2022-09-30 14:48:30 +05:30
anandbaburajan
6f8d62088e fix: mark attendance issue with joining date 2022-09-30 14:46:57 +05:30
Anand Baburajan
5d66646fcd Merge branch 'version-13-hotfix' into fix_asset_sold_status_v13 2022-09-29 22:49:10 +05:30
Sagar Sharma
6a58d15497 Merge pull request #32419 from frappe/mergify/bp/version-13-hotfix/pr-32404
fix(ux): show `Make Purchase Invoice` button based on permission (backport #32404)
2022-09-29 17:16:15 +05:30
anandbaburajan
6b21deedae chore: refactor by just using a filter 2022-09-29 15:37:04 +05:30
Sagar Sharma
7124328640 fix: show Make Purchase Invoice button based on permission
(cherry picked from commit 80080a3d7b)
2022-09-29 09:27:03 +00:00
anandbaburajan
430a4c98a0 chore: refactor by creating is_sold 2022-09-29 08:59:49 +05:30
Anand Baburajan
c65fb64578 Merge branch 'version-13-hotfix' into fix_asset_sold_status_v13 2022-09-28 21:36:48 +05:30
anandbaburajan
14ab9d9158 fix: asset requiring maintenance sold status 2022-09-28 21:25:42 +05:30
Deepesh Garg
1095052479 Merge pull request #32410 from frappe/mergify/bp/version-13-hotfix/pr-32403
fix: Disbursement Account in patch to update old loans (backport #32403)
2022-09-28 20:13:45 +05:30
Deepesh Garg
b4a511cbb4 fix: Disbursement Account in patch to update old loans
(cherry picked from commit be623ce8e8)
2022-09-28 14:06:42 +00:00
Frappe PR Bot
a77388d520 chore(release): Bumped to Version 13.39.2
## [13.39.2](https://github.com/frappe/erpnext/compare/v13.39.1...v13.39.2) (2022-09-28)

### Bug Fixes

* `For Quantity` error msg in `Stock Entry` ([a194d28](a194d28b69))
* Add return against indexes for POS Invoice ([9e8e7aa](9e8e7aab70))
* Add return against indexes for POS Invoice ([6c528d4](6c528d469d))
* allow to return expired batches using purchase return ([02468a9](02468a902f))
* consider overproduction percentage for WO finish button ([ed4ac10](ed4ac100ba))
* difference amount calculation on payment reconciliation ([d68cdb4](d68cdb4f5e))
* don't count half day in absent days in Monthly Attendance Sheet summarized view ([#32399](https://github.com/frappe/erpnext/issues/32399)) ([255aa7a](255aa7a84a))
* **e-invoicing:** local variable 'res' referenced before assignment ([#32352](https://github.com/frappe/erpnext/issues/32352)) ([8f961ab](8f961abe8b))
* get amount in words for debit note ([0cd1cac](0cd1cacc29))
* GST Itemised Sales Register GSTIN filter ([#32367](https://github.com/frappe/erpnext/issues/32367)) ([988c5b9](988c5b95e6))
* item_code key error in production plan ([638d5e9](638d5e9dc3))
* Move subscription process to hourly long quque ([447c553](447c553954))
* opening entry causing discepancy between stock and trial balance ([70c68f0](70c68f011a))
* POS only validate QTY if is_stock_item ([ac8100f](ac8100f1e5))
* POS properly validate stock for bundle products ([96fa14b](96fa14be88))
* Reduce font size for Process Statement of accounts print/pdf ([1cfeb93](1cfeb9371c))
* remove no_copy for ignore_pricing_rule ([8abfdb6](8abfdb6598))
* total value in all keys ([80d046a](80d046a38c))
2022-09-28 10:10:58 +00:00
Deepesh Garg
eda6076a43 Merge pull request #32374 from frappe/version-13-hotfix
chore: release v13
2022-09-28 15:39:01 +05:30
Deepesh Garg
0b2405bbdf Merge pull request #32395 from frappe/mergify/bp/version-13-hotfix/pr-32379
fix: POS only validate QTY if is_stock_item (backport #32379)
2022-09-28 15:12:47 +05:30
Rucha Mahabal
255aa7a84a fix: don't count half day in absent days in Monthly Attendance Sheet summarized view (#32399) 2022-09-28 15:02:21 +05:30
Deepesh Garg
a604eed1b9 chore: Resolve conflicts 2022-09-28 14:49:25 +05:30
Maharshi Patel
96fa14be88 fix: POS properly validate stock for bundle products
Stock availability was not calculated properly for Product Bundle with non stock item so i have added logic to properly calculate that as well.

(cherry picked from commit e392ea1104)

# Conflicts:
#	erpnext/selling/page/point_of_sale/pos_item_details.js
2022-09-28 08:16:02 +00:00
Maharshi Patel
ac8100f1e5 fix: POS only validate QTY if is_stock_item
POS invoice raised " Item not available " validation error even though item is non_stock.

(cherry picked from commit e39e088f18)
2022-09-28 08:16:00 +00:00
Deepesh Garg
07cc05785e Merge pull request #32386 from frappe/mergify/bp/version-13-hotfix/pr-32382
fix: Move subscription process to hourly long queue (backport #32382)
2022-09-28 10:39:08 +05:30
Deepesh Garg
f03fbc0e6d chore: Resolve conflicts 2022-09-28 08:10:37 +05:30
Deepesh Garg
447c553954 fix: Move subscription process to hourly long quque
(cherry picked from commit 82a2f31ada)

# Conflicts:
#	erpnext/hooks.py
2022-09-27 18:09:32 +00:00
Deepesh Garg
b2e9dccc8c Merge pull request #32384 from frappe/mergify/bp/version-13-hotfix/pr-32378
fix: Add return against indexes for POS Invoice (backport #32378)
2022-09-27 23:34:50 +05:30
Deepesh Garg
9e8e7aab70 fix: Add return against indexes for POS Invoice
(cherry picked from commit cbfe28286a)
2022-09-27 16:52:59 +00:00
Deepesh Garg
6c528d469d fix: Add return against indexes for POS Invoice
(cherry picked from commit 1f6205e1ea)
2022-09-27 16:52:59 +00:00
Maharshi Patel
988c5b95e6 fix: GST Itemised Sales Register GSTIN filter (#32367)
fix: GST Itemised Sales Register GSTIN filte
2022-09-27 22:21:14 +05:30
Sagar Sharma
1f2887d601 Merge pull request #32381 from frappe/mergify/bp/version-13-hotfix/pr-32377
fix: consider overproduction percentage for WO finish button (backport #32377)
2022-09-27 18:51:37 +05:30
Sagar Sharma
ed4ac100ba fix: consider overproduction percentage for WO finish button
(cherry picked from commit 05392e0918)
2022-09-27 12:15:43 +00:00
Sagar Sharma
a194d28b69 fix: For Quantity error msg in Stock Entry
(cherry picked from commit 9049db41ae)
2022-09-27 12:15:42 +00:00
rohitwaghchaure
3217924242 Merge pull request #32373 from frappe/mergify/bp/version-13-hotfix/pr-32370
fix: Not allowing to return expired batches using purchase return (backport #32370)
2022-09-27 14:44:30 +05:30
Rohit Waghchaure
02468a902f fix: allow to return expired batches using purchase return
(cherry picked from commit a4a86ee23f)
2022-09-27 08:51:26 +00:00
Maharshi Patel
a13eecc961 fix: SEZ Without Payment of Tax don't add tax rows
taxes were added even when gst_category was SEZ and export_type was Without Payment of Tax
2022-09-27 13:40:04 +05:30
rohitwaghchaure
776ee53a25 Merge pull request #32358 from frappe/mergify/bp/version-13-hotfix/pr-32346
refactor: rewrite `Item Price Stock Report` queries in `QB` (backport #32346)
2022-09-27 10:44:05 +05:30
rohitwaghchaure
ddf5565c67 Merge pull request #32366 from frappe/mergify/bp/version-13-hotfix/pr-32339
fix: opening entry causing discrepancy between stock and trial balance (backport #32339)
2022-09-27 09:57:01 +05:30
Maharshi Patel
4152a9f026 Merge branch 'version-13-hotfix' into fix-payment-reconciliation-jv-cost-center 2022-09-26 23:03:39 +05:30
Saqib Ansari
8f961abe8b fix(e-invoicing): local variable 'res' referenced before assignment (#32352) 2022-09-26 22:42:07 +05:30
Rohit Waghchaure
70c68f011a fix: opening entry causing discepancy between stock and trial balance
(cherry picked from commit bc3ab45af2)
2022-09-26 15:03:48 +00:00
Sagar Sharma
9066009e89 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32346 2022-09-26 18:18:41 +05:30
Sagar Sharma
9332e7f96f Merge pull request #32360 from frappe/mergify/bp/version-13-hotfix/pr-32347
refactor: rewrite `Incorrect Stock Value Report` queries in `QB` (backport #32347)
2022-09-26 18:18:01 +05:30
Sagar Sharma
d6a335e59f refactor: rewrite Incorrect Stock Value Report queries in QB
(cherry picked from commit b93331e844)
2022-09-26 11:40:21 +00:00
Sagar Sharma
a66002f774 refactor: rewrite Item Price Stock Report queries in QB
(cherry picked from commit 22299d2382)
2022-09-26 11:39:24 +00:00
ruthra kumar
6eb3428f8e Merge pull request #32348 from frappe/mergify/bp/version-13-hotfix/pr-32303
fix: difference amount calculation and popup on payment reconciliation (backport #32303)
2022-09-26 14:18:09 +05:30
ruthra kumar
d158784472 Merge pull request #32350 from frappe/mergify/bp/version-13-hotfix/pr-32310
fix: total value in all keys (backport #32310)
2022-09-26 12:05:04 +05:30
nishibakabeer
80d046a38c fix: total value in all keys
Gross and net profit report showing wrong values in monthly quarterly and half yearly filters which is the total value
@ruthra-kumar added in develop branch as suggested ( https://github.com/frappe/erpnext/pull/32020)

(cherry picked from commit 6919f389aa)
2022-09-26 06:03:15 +00:00
ruthra kumar
d68cdb4f5e fix: difference amount calculation on payment reconciliation
(cherry picked from commit 122d5f2729)
2022-09-26 10:36:08 +05:30
Maharshi Patel
9f988910b5 fix: payment reconciliation tool consider cost_center for JV
We need to consider cost center in all four cases of get_conditions. I have removed check in if statement for get_invoices, get_payments, get_return_invoices as it is not required.
2022-09-25 23:24:59 +05:30
Deepesh Garg
09cd480c81 Merge pull request #32333 from frappe/mergify/bp/version-13-hotfix/pr-32117
fix: Reduce font size for Process Statement of accounts print/pdf (backport #32117)
2022-09-25 17:20:57 +05:30
Sagar Sharma
4777991a74 Merge pull request #32338 from frappe/mergify/bp/version-13-hotfix/pr-32324
refactor: rewrite `Production Planning Report` queries in `QB` (backport #32324)
2022-09-23 10:38:20 +05:30
Sagar Sharma
1301a6ff7f refactor: rewrite Production Planning Report queries in QB
(cherry picked from commit 8417b9b99c)
2022-09-23 04:20:42 +00:00
Sagar Sharma
a6a5f63af2 Merge pull request #32328 from s-aga-r/backport/v13-h/32309
fix: item_code key error in production plan (backport #32309)
2022-09-23 09:48:25 +05:30
Deepesh Garg
1cfeb9371c fix: Reduce font size for Process Statement of accounts print/pdf
(cherry picked from commit 6bfd193b0d)
2022-09-22 19:38:55 +00:00
Rohit Waghchaure
638d5e9dc3 fix: item_code key error in production plan 2022-09-22 18:09:12 +05:30
Sagar Sharma
b4ee72e15e Merge pull request #32316 from s-aga-r/backport/v13-h/32304
refactor: rewrite `Exponential Smoothing Forecasting` queries in `QB` (backport #32304)
2022-09-22 11:16:22 +05:30
Sagar Sharma
2a9b519773 Merge branch 'version-13-hotfix' into backport/v13-h/32304 2022-09-22 01:32:20 +05:30
Sagar Sharma
57136fa921 Merge pull request #32315 from s-aga-r/backport/v13-h/32153
refactor: rewrite Work Order Stock Report queries in QB (backport #32153)
2022-09-22 01:31:18 +05:30
Sagar Sharma
882542c2d5 Merge branch 'version-13-hotfix' into backport/v13-h/32153 2022-09-22 01:31:04 +05:30
Sagar Sharma
0a4025e7e0 Merge pull request #32314 from s-aga-r/backport/v13-h/32161
refactor: rewrite Process Loss Report queries in QB (backport #32161)
2022-09-22 01:30:40 +05:30
Sagar Sharma
54dfd50391 refactor: rewrite Exponential Smoothing Forecasting queries in QB 2022-09-21 21:27:04 +05:30
Sagar Sharma
45d02ceb4e refactor: rewrite Work Order Stock Report queries in QB 2022-09-21 21:20:03 +05:30
Sagar Sharma
f72bb18da7 refactor: rewrite Process Loss Report queries in QB 2022-09-21 21:13:10 +05:30
Deepesh Garg
f371008cd9 Merge pull request #32293 from frappe/mergify/bp/version-13-hotfix/pr-32284
fix: remove no_copy for ignore_pricing_rule (backport #32284)
2022-09-21 16:41:25 +05:30
Deepesh Garg
82e5a784cb Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32284 2022-09-21 13:08:41 +05:30
Deepesh Garg
d267372334 Merge pull request #32299 from frappe/mergify/bp/version-13-hotfix/pr-32296
fix: get amount in words for debit note (backport #32296)
2022-09-21 13:07:39 +05:30
Sagar Sharma
92c7d074dd Merge pull request #32307 from s-aga-r/backport/13/refactor/report/exponential-smoothing-forecasting
refactor: rewrite `BOM Variance Report` queries in `QB` (backport #32297)
2022-09-21 12:42:10 +05:30
Sagar Sharma
cedc8d28e4 refactor: rewrite BOM Variance Report queries in QB 2022-09-21 12:13:05 +05:30
Frappe PR Bot
be4fdca6c7 chore(release): Bumped to Version 13.39.1
## [13.39.1](https://github.com/frappe/erpnext/compare/v13.39.0...v13.39.1) (2022-09-20)

### Bug Fixes

* Add child table for tax withheld vouchers ([4165fee](4165fee6a7))
* Change Department fieldtype to Link in Leave Balance Report ([#32252](https://github.com/frappe/erpnext/issues/32252)) ([e2e69dc](e2e69dced7))
* dont show zero qty available items in stock ageing report ([37dbc70](37dbc7043a))
* fetch description only if empty on the payment schedule ([07a7fdb](07a7fdbe6c))
* Fetch vouchers to show in Invoice ([51d7c0b](51d7c0bfe4))
* incorrect gl if tax on multi currency payment entry ([d9d6a07](d9d6a07bbb))
* item wise sales register taxes and charges ([e65990e](e65990eb34))
* make `po_detail` or `sco_rm_detail` mandatory for SE `Send to Subcontractor` ([0329642](0329642116))
* Parent Level project linkning on creating PO from project ([3c3e3cf](3c3e3cfcf8))
* pending accrual entries ([ef86b43](ef86b437cb))
* production plan pending-qty ([2dcb35d](2dcb35da33))
* remove return pos from pos reconciliation tool ([e9bf74e](e9bf74e589))
* TDS deduction via journal entry ([ca0cce7](ca0cce7599))
* use default supplier currency if default supplier is enabled ([3c10e50](3c10e5066a))
* warehouse filter in `BOM Stock Calculated Report` ([96bf1e2](96bf1e2a0a))
2022-09-20 19:12:47 +00:00
Deepesh Garg
2694438163 Merge pull request #32291 from frappe/version-13-hotfix
chore: release v13
2022-09-21 00:41:03 +05:30
Deepesh Garg
d0bd78ddcd Merge pull request #32289 from frappe/mergify/bp/version-13-hotfix/pr-32204
fix(UX): More predictable tax withholding application in invoices (backport #32204)
2022-09-21 00:11:37 +05:30
Deepesh Garg
e2912caeae chore: Handle edge cases 2022-09-20 23:50:16 +05:30
Sagar Sharma
6f9c2e6c80 Merge pull request #32302 from frappe/mergify/bp/version-13-hotfix/pr-32295
refactor: rewrite `BOM Stock Report` queries in `QB` (backport #32295)
2022-09-20 23:10:19 +05:30
Sagar Sharma
96bf1e2a0a fix: warehouse filter in BOM Stock Calculated Report
(cherry picked from commit 390ce5719d)
2022-09-20 17:37:41 +00:00
Sagar Sharma
1f633b293d refactor: rewrite BOM Stock Report queries in QB
(cherry picked from commit 8fd7c04920)
2022-09-20 17:37:40 +00:00
ruthra kumar
0cd1cacc29 fix: get amount in words for debit note
(cherry picked from commit 70f6484d9d)
2022-09-20 12:43:45 +00:00
Deepesh Garg
21154c8bee chore: fix tests 2022-09-20 17:52:37 +05:30
ruthra kumar
9270e58969 Merge pull request #32277 from frappe/remove_return_pos_from_reconciliation_tool
fix: remove return pos from pos reconciliation tool
2022-09-20 17:20:25 +05:30
Deepesh Garg
de8f44bbff chore: Resolve conflicts 2022-09-20 16:42:30 +05:30
ruthra kumar
e9bf74e589 fix: remove return pos from pos reconciliation tool 2022-09-20 16:41:28 +05:30
Maharshi Patel
8abfdb6598 fix: remove no_copy for ignore_pricing_rule
(cherry picked from commit 8c5b420aea)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/buying/doctype/purchase_order/purchase_order.json
#	erpnext/selling/doctype/quotation/quotation.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
2022-09-20 10:49:59 +00:00
Nabin Hait
305693b562 Merge pull request #32281 from maharshivpatel/fix-item-wise-sales-register-v13
fix: item wise sales register taxes and charges
2022-09-20 14:56:23 +05:30
Deepesh Garg
794eeebabc chore: resolve conflicts 2022-09-20 14:48:06 +05:30
Deepesh Garg
75768d780f Merge pull request #32282 from frappe/mergify/bp/version-13-hotfix/pr-32217
fix: incorrect gl if tax on multi currency payment entry (backport #32217)
2022-09-20 14:39:37 +05:30
Deepesh Garg
fe252b48f7 chore: fix tests
(cherry picked from commit 9aa1f84d45)
2022-09-20 09:07:06 +00:00
Deepesh Garg
ca0cce7599 fix: TDS deduction via journal entry
(cherry picked from commit 36d0906ea2)
2022-09-20 09:07:05 +00:00
Deepesh Garg
e07fd46a46 test: Add tests
(cherry picked from commit b6184ce471)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
2022-09-20 09:07:04 +00:00
Deepesh Garg
51d7c0bfe4 fix: Fetch vouchers to show in Invoice
(cherry picked from commit 3fb1595a4e)
2022-09-20 09:07:03 +00:00
Deepesh Garg
4165fee6a7 fix: Add child table for tax withheld vouchers
(cherry picked from commit 246c1a9380)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
2022-09-20 09:07:02 +00:00
Deepesh Garg
d251ef9ce5 Merge pull request #32279 from frappe/mergify/bp/version-13-hotfix/pr-32235
fix: fetch description only if empty on the payment schedule (backport #32235)
2022-09-20 14:35:35 +05:30
Sagar Sharma
afda4bdb17 Merge pull request #32286 from frappe/mergify/bp/version-13-hotfix/pr-32280
refactor: rewrite `Item Shortage Report` queries in QB (backport #32280)
2022-09-20 11:43:41 +05:30
Sagar Sharma
c4452360da test: add test cases for Item Shortage Report
(cherry picked from commit 3dc754cac2)
2022-09-20 05:45:29 +00:00
Sagar Sharma
13527d6154 refactor: rewrite Item Shortage Report queries in QB
(cherry picked from commit f0a78aa559)
2022-09-20 05:45:28 +00:00
ruthra kumar
5c0a46110e test: gl entries of payments with advance tax
(cherry picked from commit 5bd5dd7262)
2022-09-20 11:09:37 +05:30
ruthra kumar
d9d6a07bbb fix: incorrect gl if tax on multi currency payment entry
(cherry picked from commit f0ae77b23b)
2022-09-20 04:30:42 +00:00
Maharshi Patel
e65990eb34 fix: item wise sales register taxes and charges
I have added a separate column for other charges. Instead of adding all values to tax_total, it checks if account_type is tax, and then only it adds to total_tax otherwise it adds to the total_other_charges.
2022-09-20 09:47:47 +05:30
Maharshi Patel
07a7fdbe6c fix: fetch description only if empty on the payment schedule
added fetch_if_empty on description field of payment_schedule.

(cherry picked from commit f4b64686ae)
2022-09-20 03:44:50 +00:00
Sagar Sharma
2fca8b541e Merge pull request #32261 from frappe/mergify/bp/version-13-hotfix/pr-32250
fix: make `po_detail` or `sco_rm_detail` mandatory for SE Send to Subcontractor (backport #32250)
2022-09-19 19:53:57 +05:30
Sagar Sharma
7fc460bb32 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32250 2022-09-19 19:22:31 +05:30
Solufyin
e2e69dced7 fix: Change Department fieldtype to Link in Leave Balance Report (#32252)
Co-authored-by: Nihantra C. Patel <n.patel.serpentcs@gmail.com>
2022-09-19 15:42:05 +05:30
Deepesh Garg
5e49b6ea0f Merge pull request #32269 from frappe/mergify/bp/version-13-hotfix/pr-32251
fix: use default supplier currency if default supplier is enabled (backport #32251)
2022-09-19 15:20:44 +05:30
Ritwik Puri
2e3445fad9 chore: patch for removing stale values in Naming Series (#32271) 2022-09-19 13:35:08 +05:30
ruthra kumar
3c10e5066a fix: use default supplier currency if default supplier is enabled
(cherry picked from commit 77fdc37cb7)
2022-09-19 07:32:58 +00:00
Sagar Sharma
e8d2e49155 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32250 2022-09-18 20:30:21 +05:30
Deepesh Garg
5690e9771a Merge pull request #32256 from frappe/mergify/bp/version-13-hotfix/pr-32244
fix: Parent Level project linkning on creating PO from project (backport #32244)
2022-09-18 19:41:57 +05:30
Sagar Sharma
ec6cac8043 chore: conflicts 2022-09-18 16:38:09 +05:30
Sagar Sharma
0329642116 fix: make po_detail or sco_rm_detail mandatory for SE Send to Subcontractor
(cherry picked from commit b90875575c)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2022-09-18 09:25:10 +00:00
Deepesh Garg
3c3e3cfcf8 fix: Parent Level project linkning on creating PO from project
(cherry picked from commit 93e134aab0)
2022-09-17 15:00:56 +00:00
Sagar Sharma
7c4f5fa5c5 Merge pull request #32241 from frappe/mergify/bp/version-13-hotfix/pr-32236
fix: production plan pending-qty (backport #32236)
2022-09-16 20:43:28 +05:30
Sagar Sharma
367e05f808 test: update test case for production plan pending-qty
(cherry picked from commit bd6af7c613)
2022-09-16 13:00:59 +00:00
Sagar Sharma
2dcb35da33 fix: production plan pending-qty
(cherry picked from commit 5be7d42dfd)
2022-09-16 13:00:58 +00:00
Sagar Sharma
94732479f5 Merge pull request #32237 from frappe/mergify/bp/version-13-hotfix/pr-32233
refactor: rewrite Production Plan queries in QB (backport #32233)
2022-09-16 17:35:38 +05:30
Sagar Sharma
b4dec4630d chore: conflicts 2022-09-16 17:08:35 +05:30
Sagar Sharma
c415a47d25 refactor: rewrite Production Plan queries in QB
(cherry picked from commit b8cf3b4c77)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.py
2022-09-16 09:30:11 +00:00
rohitwaghchaure
1e0cc65c61 Merge pull request #32220 from frappe/mergify/bp/version-13-hotfix/pr-31681
fix: dont show zero qty available items in stock ageing report (backport #31681)
2022-09-15 12:39:22 +05:30
Rohit Waghchaure
37dbc7043a fix: dont show zero qty available items in stock ageing report
(cherry picked from commit 5da7e01db2)
2022-09-15 06:01:10 +00:00
Deepesh Garg
0e88496607 Merge pull request #32209 from frappe/mergify/bp/version-13-hotfix/pr-32208
fix: Loans pending accrual entries (backport #32208)
2022-09-14 15:34:10 +05:30
Abhinav Raut
ef86b437cb fix: pending accrual entries
(cherry picked from commit f2209045f8)
2022-09-14 08:45:27 +00:00
Frappe PR Bot
47aa191004 chore(release): Bumped to Version 13.39.0
# [13.39.0](https://github.com/frappe/erpnext/compare/v13.38.0...v13.39.0) (2022-09-13)

### Bug Fixes

* add missing warehouse filter in BOM Stock Calculated report ([ab32c09](ab32c09ff9))
* calculate amount based on payment days for statistical components too ([3f99522](3f99522764))
* conflict ([6f2a567](6f2a567c95))
* linter ([e177f1e](e177f1e51b))
* Loan amount post write off ([e78a767](e78a7679a4))
* option to start reposting from repost item valuation ([a1826f2](a1826f215a))
* pick_list - picked qty getting set to 1 ([3256e2b](3256e2b8b7))
* Purchase Order creation from Sales Order ([d79aacd](d79aacd1cb))
* Rate for internal PI have non stock UOM items ([638d6b7](638d6b7177))
* reposting not working for internal transferred purchase receipt ([bb41d8b](bb41d8bc47))
* require barcode item barcode ([#32112](https://github.com/frappe/erpnext/issues/32112)) ([44c3a32](44c3a322d3)), closes [#31957](https://github.com/frappe/erpnext/issues/31957)
* required_qty in BOM Stock Calculated report ([f2e63bc](f2e63bc491))
* **Salary Slip:** set default amount to 0 if None ([#32184](https://github.com/frappe/erpnext/issues/32184)) ([d29a033](d29a033c09))

### Features

* Ability to manually update loan amount in Salary Slips ([ac320e4](ac320e4d55))
2022-09-13 11:15:15 +00:00
Deepesh Garg
5840913320 Merge pull request #32200 from frappe/version-13-hotfix
chore: release v13
2022-09-13 16:43:21 +05:30
Deepesh Garg
b7b0076743 Merge pull request #32194 from deepeshgarg007/loan_amount_post_write_off
fix: Loan amount post write off
2022-09-13 13:41:21 +05:30
Deepesh Garg
ef5dd1d693 Merge pull request #32165 from frappe/mergify/bp/version-13-hotfix/pr-32144
fix: Rate for internal PI have non stock UOM items (backport #32144)
2022-09-13 12:02:12 +05:30
Deepesh Garg
a53b40ba93 chore: Remove print 2022-09-13 12:00:06 +05:30
Deepesh Garg
e78a7679a4 fix: Loan amount post write off 2022-09-13 11:53:11 +05:30
Deepesh Garg
6a5beecb36 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32144 2022-09-13 09:39:37 +05:30
Rucha Mahabal
2b900e2f0e Merge pull request #32187 from ruchamahabal/stat-comp-pd
fix: calculate amount based on payment days for statistical components too
2022-09-13 02:09:39 +05:30
Rucha Mahabal
bc14dbbcad refactor(tests): add a test utility file for commonly used functions in tests 2022-09-13 01:42:27 +05:30
Rucha Mahabal
f4e53a5c91 test: statistical component based on payment days 2022-09-13 00:59:59 +05:30
Rucha Mahabal
3f99522764 fix: calculate amount based on payment days for statistical components too 2022-09-13 00:59:03 +05:30
Sagar Sharma
81dd9722fe Merge pull request #32179 from Havenir/fix-picklist-picked-qty
fix: pick_list - picked qty getting set to 1
2022-09-12 22:19:26 +05:30
Sagar Sharma
9c5d335360 Merge pull request #32186 from frappe/mergify/bp/version-13-hotfix/pr-32150
refactor: BOM Stock Calculated report, fix required-qty (backport #32150)
2022-09-12 19:48:38 +05:30
Sagar Sharma
c5c28615f5 test: add test cases for BOM Stock Calculated report
(cherry picked from commit e1a98c1ff7)
2022-09-12 13:52:31 +00:00
Sagar Sharma
ab32c09ff9 fix: add missing warehouse filter in BOM Stock Calculated report
(cherry picked from commit 7a968a5f0d)
2022-09-12 13:52:30 +00:00
Sagar Sharma
f2e63bc491 fix: required_qty in BOM Stock Calculated report
(cherry picked from commit 56192daabf)
2022-09-12 13:52:30 +00:00
Sagar Sharma
882aa96973 refactor: BOM Stock Calculated report
(cherry picked from commit 723fa9eebc)
2022-09-12 13:52:29 +00:00
Rucha Mahabal
d29a033c09 fix(Salary Slip): set default amount to 0 if None (#32184) 2022-09-12 19:21:58 +05:30
Deepesh Garg
b3125a56ed Merge pull request #32178 from deepeshgarg007/manual_update_loan_amount
feat: Ability to manually update loan amount in Salary Slips
2022-09-12 17:41:02 +05:30
rohitwaghchaure
adfc57487b Merge pull request #32167 from frappe/mergify/bp/version-13-hotfix/pr-32118
fix: option to start reposting from repost item valuation (backport #32118)
2022-09-12 16:16:26 +05:30
Deepesh Garg
ac320e4d55 feat: Ability to manually update loan amount in Salary Slips 2022-09-12 15:05:42 +05:30
Ahmad
3256e2b8b7 fix: pick_list - picked qty getting set to 1 2022-09-12 14:23:44 +05:00
mergify[bot]
b4ec4ccc56 chore: correct license text for GPLv3 (backport #32170) (#32172)
* chore: correct license text for GPLv3 (#32170)

[skip ci]



Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-09-12 14:00:14 +05:30
Rohit Waghchaure
a1826f215a fix: option to start reposting from repost item valuation
(cherry picked from commit f1c4aea7b5)
2022-09-12 06:44:59 +00:00
Deepesh Garg
181c0acf99 Merge pull request #32127 from SolufyPrivateLimited/Solufy-so-to-po-v13
fix: Purchase Order creation from Sales Order
2022-09-12 09:25:24 +05:30
Deepesh Garg
638d6b7177 fix: Rate for internal PI have non stock UOM items
(cherry picked from commit 0f655e4430)
2022-09-12 03:40:23 +00:00
rohitwaghchaure
f5132411eb Merge pull request #32152 from frappe/mergify/bp/version-13-hotfix/pr-32135
fix: reposting not working for internal transferred purchase receipt (backport #32135)
2022-09-10 23:29:56 +05:30
rohitwaghchaure
e177f1e51b fix: linter 2022-09-10 23:01:48 +05:30
rohitwaghchaure
6f2a567c95 fix: conflict 2022-09-10 16:41:39 +05:30
Rohit Waghchaure
bb41d8bc47 fix: reposting not working for internal transferred purchase receipt
(cherry picked from commit a03b4ce213)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2022-09-09 17:13:21 +00:00
Nihantra C. Patel
d79aacd1cb fix: Purchase Order creation from Sales Order 2022-09-08 13:51:11 +05:30
Sagar Sharma
44c3a322d3 fix: require barcode item barcode (#32112)
fix: require barcode item barcode. (#31957)

* fix: require barcode item barcode.

* fix: make supplier mandatory in Item Supplier DocType

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2022-09-07 14:24:53 +05:30
Frappe PR Bot
2af489d69d chore(release): Bumped to Version 13.38.0
# [13.38.0](https://github.com/frappe/erpnext/compare/v13.37.0...v13.38.0) (2022-09-06)

### Bug Fixes

* consider Stock Entry purpose while getting total supplied qty ([8964612](8964612b8e))
* force delete old report docs (backport [#32026](https://github.com/frappe/erpnext/issues/32026)) ([#32028](https://github.com/frappe/erpnext/issues/32028)) ([0f1f67d](0f1f67dcae))
* incorrect import parameter for cancel PDA ([814dd36](814dd36a3e))
* key error on consolidated financial report ([20919c8](20919c8626))
* KSA VAT report multi currency amount issue ([56d8962](56d8962e40))
* Loan Interest accruals for 0 rated loans ([a369852](a3698524ca))
* lost quotation not to expired ([ea995de](ea995de4a3))
* Naming series in Journal Entry Template ([66cb3fd](66cb3fd63f))
* **pos:** error while consolidating pos invoices ([0527393](0527393b09))
* QR Code multi currency issue ([b10a2b8](b10a2b87b6))

### Features

* better Item Price list view ([#31954](https://github.com/frappe/erpnext/issues/31954)) ([25d2847](25d2847881))
2022-09-06 16:05:26 +00:00
Deepesh Garg
bb1c450c33 Merge pull request #32104 from frappe/version-13-hotfix
chore: release v13
2022-09-06 21:33:13 +05:30
Sagar Sharma
5707118d58 Merge pull request #32099 from s-aga-r/fix/stock-entry/send-to-subcontractor
fix: consider Stock Entry purpose while getting total supplied qty
2022-09-06 15:49:38 +05:30
Sagar Sharma
8964612b8e fix: consider Stock Entry purpose while getting total supplied qty 2022-09-06 13:04:03 +05:30
Deepesh Garg
514d280ee7 Merge pull request #32086 from Havenir/qr_code_multi_currency_issue
fix: QR Code multi currency issue
2022-09-05 12:42:43 +05:30
hamzaali15
b10a2b87b6 fix: QR Code multi currency issue
When try to scan qr code on app it is showing correct values for multi currencies because it is not getting base amount
2022-09-05 11:15:43 +05:00
Deepesh Garg
3664a12bb9 Merge pull request #32078 from frappe/mergify/bp/version-13-hotfix/pr-31954
feat: better Item Price list view (backport #31954)
2022-09-04 18:15:13 +05:30
Deepesh Garg
0605cbf26b Merge pull request #32076 from frappe/mergify/bp/version-13-hotfix/pr-32045
fix: Naming series in Journal Entry Template (backport #32045)
2022-09-04 18:14:40 +05:30
HENRY Florian
25d2847881 feat: better Item Price list view (#31954)
* feat: better Item Price list view

(cherry picked from commit 86395c6adb)
2022-09-04 12:38:43 +00:00
Solufyin
66cb3fd63f fix: Naming series in Journal Entry Template
(cherry picked from commit 2085626390)
2022-09-04 11:04:44 +00:00
Deepesh Garg
d22104ad40 Merge pull request #32072 from frappe/mergify/bp/version-13-hotfix/pr-31822
fix(pos): error while consolidating pos invoices (backport #31822)
2022-09-04 15:57:37 +05:30
Saqib Ansari
0527393b09 fix(pos): error while consolidating pos invoices
(cherry picked from commit 33762dbbac)
2022-09-04 07:56:06 +00:00
Deepesh Garg
741d6fcb9a Merge pull request #32053 from Havenir/version-13-hotfix
fix: KSA VAT report multi currency amount issue
2022-09-04 13:12:21 +05:30
ruthra kumar
1870dbf9a8 Merge pull request #32059 from frappe/mergify/bp/version-13-hotfix/pr-32054
fix: type error on cancellation of Process Deferred Accounting (backport #32054)
2022-09-02 17:10:01 +05:30
ruthra kumar
69a9724422 test: pda document submission and cancellation
(cherry picked from commit 1c385541fa)
2022-09-02 11:15:13 +00:00
ruthra kumar
814dd36a3e fix: incorrect import parameter for cancel PDA
(cherry picked from commit 08f2e4edc3)
2022-09-02 11:15:12 +00:00
ruthra kumar
17ad96998e Merge pull request #32057 from frappe/mergify/bp/version-13-hotfix/pr-32052
fix: key error on consolidated financial report (backport #32052)
2022-09-02 16:41:56 +05:30
ruthra kumar
20919c8626 fix: key error on consolidated financial report
accounts with same name but different account number will throw key
error on consolidated report

(cherry picked from commit 6e8395cccd)
2022-09-02 10:46:23 +00:00
hamzaali15
56d8962e40 fix: KSA VAT report multi currency amount issue
In KSA VAT report amount is not showing correctly for multi currencies because net_amount field is fetched instead of base_net_amount
2022-09-01 15:02:21 +05:00
Deepesh Garg
0a3ac82232 Merge pull request #32031 from frappe/mergify/bp/version-13-hotfix/pr-32030
fix: Loan Interest accruals for 0 rated loans (backport #32030)
2022-08-31 21:04:27 +05:30
Deepesh Garg
b736df3f0b Merge pull request #32036 from niyazrazak/patch-5
fix: lost quotation not to expired
2022-08-31 21:03:31 +05:30
MOHAMMED NIYAS
ea995de4a3 fix: lost quotation not to expired
code is merged in version-14 and conflict in version 13
2022-08-31 15:31:07 +05:30
Deepesh Garg
b7c94e38a6 chore: Add check for principal amount
(cherry picked from commit a76d3827ec)
2022-08-30 15:46:39 +00:00
Deepesh Garg
a3698524ca fix: Loan Interest accruals for 0 rated loans
(cherry picked from commit eefc9b7172)
2022-08-30 15:46:38 +00:00
mergify[bot]
0f1f67dcae fix: force delete old report docs (backport #32026) (#32028)
fix: force delete old report docs (#32026)

(cherry picked from commit ffa3071d36)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-30 15:45:47 +05:30
Frappe PR Bot
11b04acf8b chore(release): Bumped to Version 13.37.0
# [13.37.0](https://github.com/frappe/erpnext/compare/v13.36.5...v13.37.0) (2022-08-30)

### Bug Fixes

* Add docstatus filter for voucher_no in Repost Item Valuation ([2cf2885](2cf2885470))
* Cash and non trade discount calculation ([20e9599](20e9599fc1))
* do not clear promotion/transfer details if doc is amended ([#32000](https://github.com/frappe/erpnext/issues/32000)) ([074d484](074d484d3c))
* filter leave types and render leave application dashboard w/o from date ([#32001](https://github.com/frappe/erpnext/issues/32001)) ([7686c9e](7686c9e450))
* Loan Write-off for term loans ([e64e812](e64e812679))
* **minor:** don't print tax rate if its '0' ([#31838](https://github.com/frappe/erpnext/issues/31838)) ([a46dca5](a46dca57cb))
* permissions for Task Type ([#32016](https://github.com/frappe/erpnext/issues/32016)) ([1157bf8](1157bf887f))
* **pos:** edge case while closing pos (backport [#31748](https://github.com/frappe/erpnext/issues/31748)) ([#31893](https://github.com/frappe/erpnext/issues/31893)) ([261405c](261405cec1))
* Purposes not set ([edfaf99](edfaf99388))
* Route condition set for stock ledger (backport [#31935](https://github.com/frappe/erpnext/issues/31935)) ([#31946](https://github.com/frappe/erpnext/issues/31946)) ([34537c9](34537c9dc2))
* Set the condition to create a purchase receipt ([b4c992d](b4c992dd4d))
* Test cases ([7c85b48](7c85b487cd))

### Features

* In BOM,  Operation time can be fix (backport [#27063](https://github.com/frappe/erpnext/issues/27063)) ([#31923](https://github.com/frappe/erpnext/issues/31923)) ([c2b8d1b](c2b8d1bd9a))
2022-08-30 08:05:05 +00:00
Deepesh Garg
4c1156a816 Merge pull request #32023 from frappe/version-13-hotfix
chore: weekly version 13 release
2022-08-30 13:32:35 +05:30
Deepesh Garg
570729b73e Merge pull request #32024 from frappe/mergify/bp/version-13-hotfix/pr-31838
fix(minor): don't print tax rate if its '0' (backport #31838)
2022-08-30 13:32:16 +05:30
ruthra kumar
a46dca57cb fix(minor): don't print tax rate if its '0' (#31838)
(cherry picked from commit 3b4c0a3fc0)
2022-08-30 07:34:26 +00:00
Deepesh Garg
821c1cddfc Merge pull request #32011 from deepeshgarg007/loan_write_off_salary_slip
fix: Loan Write-off for term loans
2022-08-30 11:36:09 +05:30
Deepesh Garg
8217c6dd9f chore: Linting issues and test case fixes 2022-08-30 10:24:31 +05:30
Deepesh Garg
fc030a7de9 Merge pull request #32018 from frappe/mergify/bp/version-13-hotfix/pr-32016
fix: permissions for Task Type (backport #32016)
2022-08-30 08:38:25 +05:30
Raffael Meyer
1157bf887f fix: permissions for Task Type (#32016)
(cherry picked from commit 73f4d5931d)
2022-08-29 16:27:24 +00:00
Deepesh Garg
e64e812679 fix: Loan Write-off for term loans 2022-08-29 18:33:40 +05:30
Deepesh Garg
6025df97ef Merge pull request #31931 from frappe/mergify/bp/version-13-hotfix/pr-31910
fix: Cash and non trade discount calculation (backport #31910)
2022-08-29 15:06:00 +05:30
Deepesh Garg
4793adfefd chore: rounded total for cash and non trade discounts 2022-08-29 14:02:18 +05:30
Rucha Mahabal
7686c9e450 fix: filter leave types and render leave application dashboard w/o from date (#32001) 2022-08-29 12:26:05 +05:30
Rucha Mahabal
074d484d3c fix: do not clear promotion/transfer details if doc is amended (#32000) 2022-08-29 12:10:15 +05:30
Deepesh Garg
332e86af7e Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31910 2022-08-28 17:32:41 +05:30
Deepesh Garg
8eded437d2 Merge pull request #31971 from frappe/mergify/bp/version-13-hotfix/pr-31955
chore: update french translation (backport #31955)
2022-08-28 11:32:34 +05:30
Deepesh Garg
bef7f6114d Merge pull request #31989 from frappe/mergify/bp/version-13-hotfix/pr-31988
chore: remove precision on discount_percentage of Sales Invoice Item (backport #31988)
2022-08-28 11:32:14 +05:30
Deepesh Garg
4095a3dae2 chore: Resolve conflicts 2022-08-26 18:10:09 +05:30
ruthra kumar
0b40117bfd chore: remove precision on discount_percentage of Sales Invoice Item
(cherry picked from commit c42fef541a)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
2022-08-26 10:34:31 +00:00
ruthra kumar
09de1cba92 Merge pull request #31972 from frappe/mergify/bp/version-13-hotfix/pr-31934
refactor: disable discount accounting on Buying module(PI) (backport #31934)
2022-08-25 17:14:44 +05:30
ruthra kumar
51a40ad2fc test: remove discount accounting tests
(cherry picked from commit 277ef04b60)
2022-08-25 16:05:01 +05:30
mergify[bot]
261405cec1 fix(pos): edge case while closing pos (backport #31748) (#31893) 2022-08-25 14:50:24 +05:30
ruthra kumar
1ac9d7f43e refactor: disable discount accounting on Buying module(PI)
(cherry picked from commit a956e20f29)
2022-08-25 14:44:54 +05:30
Florian HENRY
69b3145be4 chore: update fr translation
(cherry picked from commit 299da5d596)
2022-08-25 07:37:26 +00:00
Florian HENRY
5e1d367d7a chore: update fr translation
(cherry picked from commit 1f6f2747d4)
2022-08-25 07:37:25 +00:00
Florian HENRY
10f0baf6ad chore: update french translation
(cherry picked from commit 264f98af14)
2022-08-25 07:37:25 +00:00
Deepesh Garg
20694eb509 Merge pull request #31965 from frappe/mergify/bp/version-13-hotfix/pr-31909
fix: Add docstatus filter for voucher_no in Repost Item Valuation (backport #31909)
2022-08-25 11:25:14 +05:30
HENRY Florian
c2b8d1bd9a feat: In BOM, Operation time can be fix (backport #27063) (#31923)
* feat: In BOM Operation time can be fix (backport #27063)

* chore: only changes necessary for feature

* chore: add test

* chore: linter

* chore: linter

* chore: linter

* chore: fix date in json

* chore: fix test order

* chore: revert test from version-13 not develop

* chore: make test ok
2022-08-25 11:14:35 +05:30
rohitwaghchaure
49bf05c1c2 Merge pull request #31962 from frappe/mergify/bp/version-13-hotfix/pr-31951
fix: Purposes not set in Maintenance Visit (backport #31951)
2022-08-25 10:51:12 +05:30
Sagar Sharma
2cf2885470 fix: Add docstatus filter for voucher_no in Repost Item Valuation
(cherry picked from commit 520306dc87)
2022-08-25 05:20:46 +00:00
Rohit Waghchaure
edfaf99388 fix: Purposes not set
(cherry picked from commit f9a7b31b5b)
2022-08-25 05:19:36 +00:00
Deepesh Garg
3a7a53ab72 Merge pull request #31919 from SolufyPrivateLimited/Solufy-pur_inv_docstatus
fix: Set the condition to create a purchase receipt
2022-08-25 10:35:14 +05:30
mergify[bot]
34537c9dc2 fix: Route condition set for stock ledger (backport #31935) (#31946)
fix: Route condition set for stock ledger (#31935)

(cherry picked from commit 0e26df331c)

Co-authored-by: Solufyin <34390782+Solufyin@users.noreply.github.com>
2022-08-24 13:30:49 +05:30
Deepesh Garg
6a9d13ff28 chore: Linting issues
(cherry picked from commit 1cb7ae16ab)
2022-08-23 04:50:19 +00:00
Deepesh Garg
7c85b487cd fix: Test cases
(cherry picked from commit ae3dce0cbd)
2022-08-23 04:50:18 +00:00
Deepesh Garg
20e9599fc1 fix: Cash and non trade discount calculation
(cherry picked from commit 3b15966cc9)
2022-08-23 04:50:17 +00:00
Solufyin
b4c992dd4d fix: Set the condition to create a purchase receipt 2022-08-22 12:00:21 +05:30
164 changed files with 4098 additions and 2950 deletions

View File

@@ -93,7 +93,7 @@ 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
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 2 --build-number ${{ matrix.container }}'
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}

View File

@@ -12,17 +12,13 @@ erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @marination @rohitwaghchaure @s-aga-r
erpnext/e_commerce/ @marination
erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r
erpnext/portal/ @marination
erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @s-aga-r
erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev
erpnext/healthcare/ @chillaranand
erpnext/hr/ @ruchamahabal
erpnext/non_profit/ @ruchamahabal
@@ -30,7 +26,7 @@ erpnext/payroll @ruchamahabal
erpnext/projects/ @ruchamahabal
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination rohitwaghchaure
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/public/ @nextchamp-saqib @marination
.github/ @ankush

View File

@@ -65,6 +65,8 @@ GNU/General Public License (see [license.txt](license.txt))
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
---
## Contributing

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.36.5"
__version__ = "13.40.2"
def get_default_company(user=None):

View File

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

View File

@@ -173,8 +173,8 @@ frappe.ui.form.on("Journal Entry", {
var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
row.account = d.account;
row.balance = d.balance;
frappe.model.set_value(row.doctype, row.name, "account", d.account)
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
});
refresh_field("accounts");
}

View File

@@ -194,7 +194,9 @@ class JournalEntry(AccountsController):
}
)
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
inv, self.tax_withholding_category
)
if not tax_withholding_details:
return

View File

@@ -2,7 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on("Journal Entry Template", {
setup: function(frm) {
refresh: function(frm) {
frappe.model.set_default_values(frm.doc);
frm.set_query("account" ,"accounts", function(){

View File

@@ -1111,7 +1111,7 @@ frappe.ui.form.on('Payment Entry', {
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
frm.doc.paid_amount_after_tax = frm.doc.base_paid_amount;
});
},
@@ -1202,7 +1202,7 @@ frappe.ui.form.on('Payment Entry', {
}
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
frm.doc.paid_amount_after_tax = flt(frm.doc.base_paid_amount/(1+cumulated_tax_fraction))
});
},
@@ -1234,6 +1234,7 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.total_taxes_and_charges = 0.0;
frm.doc.base_total_taxes_and_charges = 0.0;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
let actual_tax_dict = {};
// maintain actual tax rate based on idx
@@ -1254,8 +1255,8 @@ frappe.ui.form.on('Payment Entry', {
}
}
tax.tax_amount = current_tax_amount;
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
// tax accounts are only in company currency
tax.base_tax_amount = current_tax_amount;
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
if(i==0) {
@@ -1264,9 +1265,29 @@ frappe.ui.form.on('Payment Entry', {
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
}
tax.base_total = tax.total * frm.doc.source_exchange_rate;
frm.doc.total_taxes_and_charges += current_tax_amount;
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
// tac accounts are only in company currency
tax.base_total = tax.total
// calculate total taxes and base total taxes
if(frm.doc.payment_type == "Pay") {
// tax accounts only have company currency
if(tax.currency != frm.doc.paid_to_account_currency) {
//total_taxes_and_charges has the target currency. so using target conversion rate
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.target_exchange_rate);
} else {
frm.doc.total_taxes_and_charges += current_tax_amount;
}
} else if(frm.doc.payment_type == "Receive") {
if(tax.currency != frm.doc.paid_from_account_currency) {
//total_taxes_and_charges has the target currency. so using source conversion rate
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.source_exchange_rate);
} else {
frm.doc.total_taxes_and_charges += current_tax_amount;
}
}
frm.doc.base_total_taxes_and_charges += tax.base_tax_amount;
frm.refresh_field('taxes');
frm.refresh_field('total_taxes_and_charges');

View File

@@ -944,6 +944,13 @@ class PaymentEntry(AccountsController):
)
if not d.included_in_paid_amount:
if get_account_currency(payment_account) != self.company_currency:
if self.payment_type == "Receive":
exchange_rate = self.target_exchange_rate
elif self.payment_type in ["Pay", "Internal Transfer"]:
exchange_rate = self.source_exchange_rate
base_tax_amount = flt((tax_amount / exchange_rate), self.precision("paid_amount"))
gl_entries.append(
self.get_gl_dict(
{
@@ -1059,7 +1066,7 @@ class PaymentEntry(AccountsController):
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
self.paid_amount_after_tax = self.paid_amount
self.paid_amount_after_tax = self.base_paid_amount
def determine_exclusive_rate(self):
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
@@ -1078,7 +1085,7 @@ class PaymentEntry(AccountsController):
cumulated_tax_fraction += tax.tax_fraction_for_current_item
self.paid_amount_after_tax = flt(self.paid_amount / (1 + cumulated_tax_fraction))
self.paid_amount_after_tax = flt(self.base_paid_amount / (1 + cumulated_tax_fraction))
def calculate_taxes(self):
self.total_taxes_and_charges = 0.0
@@ -1101,7 +1108,7 @@ class PaymentEntry(AccountsController):
current_tax_amount += actual_tax_dict[tax.idx]
tax.tax_amount = current_tax_amount
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
tax.base_tax_amount = current_tax_amount
if tax.add_deduct_tax == "Deduct":
current_tax_amount *= -1.0
@@ -1115,14 +1122,20 @@ class PaymentEntry(AccountsController):
self.get("taxes")[i - 1].total + current_tax_amount, self.precision("total", tax)
)
tax.base_total = tax.total * self.source_exchange_rate
tax.base_total = tax.total
if self.payment_type == "Pay":
self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
if tax.currency != self.paid_to_account_currency:
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.total_taxes_and_charges += current_tax_amount
elif self.payment_type == "Receive":
if tax.currency != self.paid_from_account_currency:
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
else:
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += tax.base_tax_amount
if self.get("taxes"):
self.paid_amount_after_tax = self.get("taxes")[-1].base_total

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe import qb
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -743,6 +744,46 @@ class TestPaymentEntry(unittest.TestCase):
flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
)
def test_gl_of_multi_currency_payment_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": 100,
"add_deduct_tax": "Add",
"description": "Test",
},
)
payment_entry.target_exchange_rate = 80
payment_entry.received_amount = 12.5
payment_entry = payment_entry.submit()
gle = qb.DocType("GL Entry")
gl_entries = (
qb.from_(gle)
.select(
gle.account,
gle.debit,
gle.credit,
gle.debit_in_account_currency,
gle.credit_in_account_currency,
)
.orderby(gle.account)
.where(gle.voucher_no == payment_entry.name)
.run()
)
expected_gl_entries = (
("_Test Account Service Tax - _TC", 100.0, 0.0, 100.0, 0.0),
("_Test Bank - _TC", 0.0, 1100.0, 0.0, 1100.0),
("_Test Payable USD - _TC", 1000.0, 0.0, 12.5, 0),
)
self.assertEqual(gl_entries, expected_gl_entries)
def test_payment_entry_against_onhold_purchase_invoice(self):
pi = make_purchase_invoice()

View File

@@ -8,7 +8,11 @@ from frappe.model.document import Document
from frappe.utils import flt, getdate, nowdate, today
import erpnext
from erpnext.accounts.utils import get_outstanding_invoices, reconcile_against_document
from erpnext.accounts.utils import (
get_outstanding_invoices,
reconcile_against_document,
update_reference_in_payment_entry,
)
from erpnext.controllers.accounts_controller import get_advance_payment_entries
@@ -190,6 +194,23 @@ class PaymentReconciliation(Document):
inv.currency = entry.get("currency")
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
def get_difference_amount(self, allocated_entry):
if allocated_entry.get("reference_type") != "Payment Entry":
return
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
row = self.get_payment_details(allocated_entry, dr_or_cr)
doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name)
update_reference_in_payment_entry(row, doc, do_not_save=True)
return doc.difference_amount
@frappe.whitelist()
def allocate_entries(self, args):
self.validate_entries()
@@ -205,12 +226,16 @@ class PaymentReconciliation(Document):
res = self.get_allocated_entry(pay, inv, pay["amount"])
inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
pay["amount"] = 0
res.difference_amount = self.get_difference_amount(res)
if pay.get("amount") == 0:
entries.append(res)
break
elif inv.get("outstanding_amount") == 0:
entries.append(res)
continue
else:
break
@@ -332,7 +357,7 @@ class PaymentReconciliation(Document):
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
condition = " and company = '{0}' ".format(self.company)
if self.get("cost_center") and (get_invoices or get_payments or get_return_invoices):
if self.get("cost_center"):
condition = " and cost_center = '{0}' ".format(self.cost_center)
if get_invoices:

View File

@@ -186,8 +186,10 @@
{
"fetch_from": "bank_account.bank",
"fieldname": "bank",
"fieldtype": "Read Only",
"label": "Bank"
"fieldtype": "Link",
"label": "Bank",
"options": "Bank",
"read_only": 1
},
{
"fetch_from": "bank_account.bank_account_no",
@@ -366,10 +368,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-09-18 12:24:14.178853",
"modified": "2022-09-30 16:19:43.680025",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -401,5 +404,6 @@
}
],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -39,6 +39,7 @@
{
"columns": 2,
"fetch_from": "payment_term.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
@@ -159,7 +160,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-28 05:41:35.084233",
"modified": "2022-09-16 13:57:06.382859",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -168,5 +169,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

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

View File

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

View File

@@ -1572,7 +1572,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2022-03-22 13:00:24.166684",
"modified": "2022-09-27 13:00:24.166684",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice):
frappe.bold(d.warehouse),
frappe.bold(d.qty),
)
if flt(available_stock) <= 0:
if is_stock_item and flt(available_stock) <= 0:
frappe.throw(
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
elif flt(available_stock) < flt(d.qty):
elif is_stock_item and flt(available_stock) < flt(d.qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -634,11 +634,12 @@ def get_stock_availability(item_code, warehouse):
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
return bin_qty - pos_sales_qty, is_stock_item
else:
is_stock_item = False
is_stock_item = True
if frappe.db.exists("Product Bundle", item_code):
return get_bundle_availability(item_code, warehouse), is_stock_item
else:
# Is a service item
is_stock_item = False
# Is a service item or non_stock item
return 0, is_stock_item
@@ -652,7 +653,9 @@ def get_bundle_availability(bundle_item_code, warehouse):
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.qty
if bundle_bin_qty > max_available_bundles:
if bundle_bin_qty > max_available_bundles and frappe.get_value(
"Item", item.item_code, "is_stock_item"
):
bundle_bin_qty = max_available_bundles
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
@@ -744,3 +747,7 @@ def add_return_modes(doc, pos_profile):
]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
append_payment(payment_mode[0])
def on_doctype_update():
frappe.db.add_index("POS Invoice", ["return_against"])

View File

@@ -495,6 +495,67 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, pos.submit)
def test_value_error_on_serial_no_validation(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item(
company="_Test Company",
target_warehouse="Stores - _TC",
cost_center="Main - _TC",
expense_account="Cost of Goods Sold - _TC",
)
serial_nos = se.get("items")[0].serial_no
# make a pos invoice
pos = create_pos_invoice(
company="_Test Company",
debit_to="Debtors - _TC",
account_for_change_amount="Cash - _TC",
warehouse="Stores - _TC",
income_account="Sales - _TC",
expense_account="Cost of Goods Sold - _TC",
cost_center="Main - _TC",
item=se.get("items")[0].item_code,
rate=1000,
qty=1,
do_not_save=1,
)
pos.get("items")[0].has_serial_no = 1
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos = pos.save().submit()
# make a return
pos_return = make_sales_return(pos.name)
pos_return.paid_amount = pos_return.grand_total
pos_return.save()
pos_return.submit()
# set docstatus to 2 for pos to trigger this issue
frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2)
pos2 = create_pos_invoice(
company="_Test Company",
debit_to="Debtors - _TC",
account_for_change_amount="Cash - _TC",
warehouse="Stores - _TC",
income_account="Sales - _TC",
expense_account="Cost of Goods Sold - _TC",
cost_center="Main - _TC",
item=se.get("items")[0].item_code,
rate=1000,
qty=1,
do_not_save=1,
)
pos2.get("items")[0].has_serial_no = 1
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
# Value error should not be triggered on validation
pos2.save()
def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,

View File

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

View File

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

View File

@@ -43,6 +43,7 @@
"currency",
"write_off_account",
"write_off_cost_center",
"write_off_limit",
"account_for_change_amount",
"disable_rounded_total",
"column_break_23",
@@ -360,6 +361,14 @@
"fieldtype": "Check",
"label": "Validate Stock on Save"
},
{
"default": "1",
"description": "Auto write off precision loss while consolidation",
"fieldname": "write_off_limit",
"fieldtype": "Currency",
"label": "Write Off Limit",
"reqd": 1
},
{
"default": "0",
"description": "If enabled, the consolidated invoices will have rounded total disabled",
@@ -393,7 +402,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2022-07-21 11:16:46.911173",
"modified": "2022-08-10 12:57:06.241439",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -269,6 +269,18 @@ def get_serial_no_for_item(args):
return item_details
def update_pricing_rule_uom(pricing_rule, args):
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
pricing_rule.apply_on
)
apply_on_field = frappe.scrub(pricing_rule.apply_on)
for row in pricing_rule.get(child_doc):
if row.get(apply_on_field) == args.get(apply_on_field):
pricing_rule.uom = row.uom
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
@@ -325,7 +337,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if isinstance(pricing_rule, string_types):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
update_pricing_rule_uom(pricing_rule, args)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
if pricing_rule.get("suggestion"):
continue
@@ -439,12 +452,15 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.currency == args.currency:
pricing_rule_rate = pricing_rule.rate
# TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way.
if pricing_rule_rate:
is_blank_uom = pricing_rule.get("uom") != args.get("uom")
# Override already set price list rate (from item price)
# if pricing_rule_rate > 0
item_details.update(
{
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
"price_list_rate": pricing_rule_rate
* (args.get("conversion_factor", 1) if is_blank_uom else 1),
}
)
item_details.update({"discount_percentage": 0.0})

View File

@@ -597,6 +597,121 @@ class TestPricingRule(unittest.TestCase):
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()
def test_item_price_with_blank_uom_pricing_rule(self):
properties = {
"item_code": "Item Blank UOM",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item Blank UOM", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item Blank UOM Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item Blank UOM",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
self.assertEqual(si.items[0].price_list_rate, 1010)
self.assertEqual(si.items[0].rate, 1010)
si.delete()
si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete()
item.delete()
def test_item_price_with_selling_uom_pricing_rule(self):
properties = {
"item_code": "Item UOM other than Stock",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)
make_item_price("Item UOM other than Stock", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item UOM other than Stock Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item UOM other than Stock",
"uom": "Box",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(
do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so apply pricing_rule only on Box UOM.
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)
si.delete()
si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
self.assertEqual(si.items[0].price_list_rate, 100)
self.assertEqual(si.items[0].rate, 100)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete()
item.delete()
def test_pricing_rule_for_different_currency(self):
make_item("Test Sanitizer Item")

View File

@@ -111,6 +111,12 @@ def _get_pricing_rules(apply_on, args, values):
)
if apply_on_field == "item_code":
if args.get("uom", None):
item_conditions += (
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
)
)
if "variant_of" not in args:
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")

View File

@@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
)
make_gl_entries(gl_entries=gl_entries, cancel=1)
make_gl_entries(gl_map=gl_entries, cancel=1)

View File

@@ -57,3 +57,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
]
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
posting_date="2019-01-01",
start_date="2019-01-01",
end_date="2019-01-31",
type="Income",
)
)
pda.submit()
pda.cancel()

View File

@@ -25,7 +25,7 @@
</div>
<br>
<table class="table table-bordered">
<table class="table table-bordered" style="font-size: 10px">
<thead>
<tr>
<th style="width: 12%">{{ _("Date") }}</th>

View File

@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
this._super();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
@@ -539,7 +539,7 @@ frappe.ui.form.on("Purchase Invoice", {
},
add_custom_buttons: function(frm) {
if (frm.doc.per_received < 100) {
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
frm.add_custom_button(__('Purchase Receipt'), () => {
frm.events.make_purchase_receipt(frm);
}, __('Create'));

View File

@@ -83,6 +83,8 @@
"section_break_51",
"taxes_and_charges",
"taxes",
"tax_withheld_vouchers_section",
"tax_withheld_vouchers",
"sec_tax_breakup",
"other_charges_calculation",
"totals",
@@ -511,7 +513,6 @@
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
"print_hide": 1
},
@@ -1417,13 +1418,26 @@
"label": "Advance Tax",
"options": "Advance Tax",
"read_only": 1
},
{
"fieldname": "tax_withheld_vouchers_section",
"fieldtype": "Section Break",
"label": "Tax Withheld Vouchers"
},
{
"fieldname": "tax_withheld_vouchers",
"fieldtype": "Table",
"label": "Tax Withheld Vouchers",
"no_copy": 1,
"options": "Tax Withheld Vouchers",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2021-11-25 13:31:02.716727",
"modified": "2022-10-07 14:19:14.214157",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1483,7 +1497,8 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -567,7 +567,6 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
@@ -696,6 +695,10 @@ class PurchaseInvoice(BuyingController):
)
)
credit_amount = item.base_net_amount
if self.is_internal_supplier and item.valuation_rate:
credit_amount = flt(item.valuation_rate * item.stock_qty)
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
gl_entries.append(
self.get_gl_dict(
@@ -705,7 +708,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
},
warehouse_account[item.from_warehouse]["account_currency"],
item=item,
@@ -794,7 +797,7 @@ class PurchaseInvoice(BuyingController):
)
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting)
dummy, amount = self.get_amount_and_base_amount(item, None)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@@ -1110,7 +1113,7 @@ class PurchaseInvoice(BuyingController):
valuation_tax = {}
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
amount, base_amount = self.get_tax_amounts(tax, None)
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head)
@@ -1365,7 +1368,14 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, "status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Purchase Invoice",
)
self.update_advance_tax_references(cancel=1)
def update_project(self):
@@ -1458,7 +1468,7 @@ class PurchaseInvoice(BuyingController):
if not self.tax_withholding_category:
return
tax_withholding_details, advance_taxes = get_party_tax_withholding_details(
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
self, self.tax_withholding_category
)
@@ -1487,6 +1497,19 @@ class PurchaseInvoice(BuyingController):
for d in to_remove:
self.remove(d)
## Add pending vouchers on which tax was withheld
self.set("tax_withheld_vouchers", [])
for voucher_no, voucher_details in voucher_wise_amount.items():
self.append(
"tax_withheld_vouchers",
{
"voucher_name": voucher_no,
"voucher_type": voucher_details.get("voucher_type"),
"taxable_amount": voucher_details.get("amount"),
},
)
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()

View File

@@ -304,59 +304,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
discount_account = create_account(
account_name="Discount Account",
parent_account="Indirect Expenses - _TC",
company="_Test Company",
)
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 225.0, nowdate()],
["Discount Account - _TC", 0.0, 25.0, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
enable_discount_accounting(enable=0)
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
additional_discount_account = create_account(
account_name="Discount Account",
parent_account="Indirect Expenses - _TC",
company="_Test Company",
)
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
pi.apply_discount_on = "Grand Total"
pi.additional_discount_account = additional_discount_account
pi.additional_discount_percentage = 10
pi.disable_rounded_total = 1
pi.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10,
},
)
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 247.5, nowdate()],
["Discount Account - _TC", 0.0, 27.5, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
@@ -1602,6 +1549,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
pi.save()
self.assertEqual(pi.items[0].conversion_factor, 1000)
def test_batch_expiry_for_purchase_invoice(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
item = self.make_item(
"_Test Batch Item For Return Check",
{
"is_purchase_item": 1,
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TBIRC.#####",
},
)
pi = make_purchase_invoice(
qty=1,
item_code=item.name,
update_stock=True,
)
pi.load_from_db()
batch_no = pi.items[0].batch_no
self.assertTrue(batch_no)
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
return_pi = make_return_doc(pi.doctype, pi.name)
return_pi.save().submit()
self.assertTrue(return_pi.docstatus == 1)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -706,6 +706,7 @@
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -871,7 +872,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-11-15 17:04:07.191013",
"modified": "2022-10-12 03:37:29.032732",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -480,9 +480,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
is_cash_or_non_trade_discount() {
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount);
if (!this.frm.doc.is_cash_or_non_trade_discount) {
this.frm.set_value("additional_discount_account", "");
}
this.calculate_taxes_and_totals();
}
});

View File

@@ -651,7 +651,6 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Ignore Pricing Rule",
"no_copy": 1,
"print_hide": 1
},
{
@@ -2046,7 +2045,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-07-11 17:43:56.435382",
"modified": "2022-09-16 17:44:22.227332",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1065,22 +1065,6 @@ class SalesInvoice(SellingController):
)
)
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
gl_entries.append(
self.get_gl_dict(
{
"account": self.additional_discount_account,
"against": self.debit_to,
"debit": self.base_discount_amount,
"debit_in_account_currency": self.discount_amount,
"cost_center": self.cost_center,
"project": self.project,
},
self.currency,
item=self,
)
)
def make_tax_gl_entries(self, gl_entries):
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import make_autoname
from frappe.utils import add_days, flt, getdate, nowdate
from frappe.utils import add_days, flt, getdate, nowdate, today
from six import iteritems
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
@@ -31,10 +31,20 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
get_qty_after_transaction,
make_stock_entry,
)
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class TestSalesInvoice(unittest.TestCase):
def setUp(self):
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
def make(self):
w = frappe.copy_doc(test_records[0])
w.is_pos = 0
@@ -1687,7 +1697,7 @@ class TestSalesInvoice(unittest.TestCase):
si.save()
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
def test_outstanding_amount_after_advance_jv_cancelation(self):
def test_outstanding_amount_after_advance_jv_cancellation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
)
@@ -1731,7 +1741,7 @@ class TestSalesInvoice(unittest.TestCase):
flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
)
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
@@ -2369,29 +2379,6 @@ class TestSalesInvoice(unittest.TestCase):
acc_settings.save()
def test_inter_company_transaction(self):
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
create_internal_customer(
customer_name="_Test Internal Customer",
represents_company="_Test Company 1",
allowed_to_interact_with="Wind Power LLC",
)
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
supplier = frappe.get_doc(
{
"supplier_group": "_Test Supplier Group",
"supplier_name": "_Test Internal Supplier",
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": "Wind Power LLC",
}
)
supplier.append("companies", {"company": "_Test Company 1"})
supplier.insert()
si = create_sales_invoice(
company="Wind Power LLC",
customer="_Test Internal Customer",
@@ -2451,34 +2438,9 @@ class TestSalesInvoice(unittest.TestCase):
se.cancel()
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1",
company="_Test Company with perpetual inventory",
)
frappe.db.set_value(
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
)
customer = create_internal_customer(
"_Test Internal Customer 2",
"_Test Company with perpetual inventory",
"_Test Company with perpetual inventory",
)
create_internal_supplier(
"_Test Internal Supplier 2",
"_Test Company with perpetual inventory",
"_Test Company with perpetual inventory",
)
si = create_sales_invoice(
company="_Test Company with perpetual inventory",
customer=customer,
customer="_Test Internal Customer 2",
debit_to="Debtors - TCP1",
warehouse="Stores - TCP1",
income_account="Sales - TCP1",
@@ -2492,7 +2454,7 @@ class TestSalesInvoice(unittest.TestCase):
si.update_stock = 1
si.items[0].target_warehouse = "Work In Progress - TCP1"
# Add stock to stores for succesful stock transfer
# Add stock to stores for successful stock transfer
make_stock_entry(
target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
)
@@ -2830,6 +2792,77 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
def test_internal_transfer_gl_precision_issues(self):
# Make a stock queue of an item with two valuations
# Remove all existing stock for this
if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"):
create_stock_reconciliation(
item_code="_Test Internal Transfer Item",
warehouse="Stores - TCP1",
qty=0,
rate=0,
company="_Test Company with perpetual inventory",
expense_account="Stock Adjustment - TCP1"
if frappe.get_all("Stock Ledger Entry")
else "Temporary Opening - TCP1",
posting_date="2020-04-10",
posting_time="14:00",
)
make_stock_entry(
item_code="_Test Internal Transfer Item",
target="Stores - TCP1",
qty=9000000,
basic_rate=52.0,
posting_date="2020-04-10",
posting_time="14:00",
)
make_stock_entry(
item_code="_Test Internal Transfer Item",
target="Stores - TCP1",
qty=60000000,
basic_rate=52.349777,
posting_date="2020-04-10",
posting_time="14:00",
)
# Make an internal transfer Sales Invoice Stock in non stock uom to check
# for rounding errors while converting to stock uom
si = create_sales_invoice(
company="_Test Company with perpetual inventory",
customer="_Test Internal Customer 2",
item_code="_Test Internal Transfer Item",
qty=5000000,
uom="Box",
debit_to="Debtors - TCP1",
warehouse="Stores - TCP1",
income_account="Sales - TCP1",
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
currency="INR",
do_not_save=1,
)
# Check GL Entries with precision
si.update_stock = 1
si.items[0].target_warehouse = "Work In Progress - TCP1"
si.items[0].conversion_factor = 10
si.save()
si.submit()
# Check if adjustment entry is created
self.assertTrue(
frappe.db.exists(
"GL Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"remarks": "Rounding gain/loss Entry for Stock Transfer",
},
)
)
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@@ -3278,7 +3311,7 @@ class TestSalesInvoice(unittest.TestCase):
[deferred_account, 2022.47, 0.0, "2019-03-15"],
]
gl_entries = gl_entries = frappe.db.sql(
gl_entries = frappe.db.sql(
"""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
@@ -3396,6 +3429,37 @@ class TestSalesInvoice(unittest.TestCase):
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
)
def test_batch_expiry_for_sales_invoice_return(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.item.test_item import make_item
item = make_item(
"_Test Batch Item For Return Check",
{
"is_purchase_item": 1,
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TBIRC.#####",
},
)
pr = make_purchase_receipt(qty=1, item_code=item.name)
batch_no = pr.items[0].batch_no
si = create_sales_invoice(qty=1, item_code=item.name, update_stock=1, batch_no=batch_no)
si.load_from_db()
batch_no = si.items[0].batch_no
self.assertTrue(batch_no)
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
return_si = make_return_doc(si.doctype, si.name)
return_si.save().submit()
self.assertTrue(return_si.docstatus == 1)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
@@ -3652,6 +3716,7 @@ def create_sales_invoice(**args):
"description": args.description or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"target_warehouse": args.target_warehouse,
"qty": args.qty or 1,
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
@@ -3664,8 +3729,9 @@ def create_sales_invoice(**args):
"discount_amount": args.discount_amount or 0,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"conversion_factor": 1,
"conversion_factor": args.get("conversion_factor", 1),
"incoming_rate": args.incoming_rate or 0,
"batch_no": args.batch_no or None,
},
)
@@ -3777,6 +3843,34 @@ def get_taxes_and_charges():
]
def create_internal_parties():
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
create_internal_customer(
customer_name="_Test Internal Customer",
represents_company="_Test Company 1",
allowed_to_interact_with="Wind Power LLC",
)
create_internal_customer(
customer_name="_Test Internal Customer 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_supplier(
supplier_name="_Test Internal Supplier",
represents_company="Wind Power LLC",
allowed_to_interact_with="_Test Company 1",
)
create_internal_supplier(
supplier_name="_Test Internal Supplier 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc(
@@ -3799,6 +3893,19 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
return supplier_name
def setup_accounts():
## Create internal transfer account
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1",
company="_Test Company with perpetual inventory",
)
frappe.db.set_value(
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
)
def add_taxes(doc):
doc.append(
"taxes",

View File

@@ -279,7 +279,6 @@
"label": "Discount (%) on Price List Rate with Margin",
"oldfieldname": "adj_rate",
"oldfieldtype": "Float",
"precision": "2",
"print_hide": 1
},
{
@@ -813,6 +812,8 @@
"fieldtype": "Currency",
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1
},
{
@@ -842,7 +843,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-02-24 14:41:36.392560",
"modified": "2022-10-10 20:57:38.340026",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -0,0 +1,49 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2022-09-13 16:18:59.404842",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"voucher_type",
"voucher_name",
"taxable_amount"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "voucher_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Voucher Name",
"options": "voucher_type"
},
{
"fieldname": "taxable_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Taxable Amount",
"options": "Company:company:default_currency"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-13 23:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

@@ -100,7 +100,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
).format(tax_withholding_category, inv.company, party)
)
tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount = get_tax_amount(
party_type, parties, inv, tax_details, posting_date, pan_no
)
@@ -110,7 +110,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
return tax_row
@@ -208,7 +208,9 @@ def get_lower_deduction_certificate(tax_details, pan_no):
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type)
vouchers, voucher_wise_amount = get_invoice_vouchers(
parties, tax_details, inv.company, party_type=party_type
)
advance_vouchers = get_advance_vouchers(
parties,
company=inv.company,
@@ -227,6 +229,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
tax_amount = 0
if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted:
@@ -237,6 +240,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
)
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
# once tds is deducted, not need to add vouchers in the invoice
voucher_wise_amount = {}
else:
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
@@ -252,12 +258,13 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if cint(tax_details.round_off_tax_amount):
tax_amount = round(tax_amount)
return tax_amount, tax_deducted, tax_deducted_on_advances
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
dr_or_cr = "credit" if party_type == "Supplier" else "debit"
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
voucher_wise_amount = {}
vouchers = []
filters = {
"company": company,
@@ -272,29 +279,40 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])
journal_entries = frappe.db.sql(
for d in invoices_details:
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}})
journal_entries_details = frappe.db.sql(
"""
SELECT j.name
SELECT j.name, ja.credit - ja.debit AS amount
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
WHERE
j.docstatus = 1
j.name = ja.parent
AND j.docstatus = 1
AND j.is_opening = 'No'
AND j.posting_date between %s and %s
AND ja.{dr_or_cr} > 0
AND ja.party in %s
""".format(
dr_or_cr=dr_or_cr
AND j.apply_tds = 1
AND j.tax_withholding_category = %s
""",
(
tax_details.from_date,
tax_details.to_date,
tuple(parties),
tax_details.get("tax_withholding_category"),
),
(tax_details.from_date, tax_details.to_date, tuple(parties)),
as_list=1,
as_dict=1,
)
if journal_entries:
journal_entries = journal_entries[0]
if journal_entries_details:
for d in journal_entries_details:
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}})
return invoices + journal_entries
return vouchers, voucher_wise_amount
def get_advance_vouchers(
@@ -311,6 +329,9 @@ def get_advance_vouchers(
"party": ["in", parties],
}
if party_type == "Customer":
filters.update({"against_voucher": ["is", "not set"]})
if company:
filters["company"] = company
if from_date and to_date:
@@ -320,23 +341,25 @@ def get_advance_vouchers(
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
advances = [d.reference_name for d in inv.get("advances")]
tax_info = []
if advances:
pe = frappe.qb.DocType("Payment Entry").as_("pe")
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
if inv.get("advances"):
advances = [d.reference_name for d in inv.get("advances")]
tax_info = (
frappe.qb.from_(at)
.inner_join(pe)
.on(pe.name == at.parent)
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
.where(at.parent.isin(advances))
.where(at.account_head == tax_details.account_head)
.run(as_dict=True)
)
if advances:
pe = frappe.qb.DocType("Payment Entry").as_("pe")
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
tax_info = (
frappe.qb.from_(at)
.inner_join(pe)
.on(pe.name == at.parent)
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
.where(at.parent.isin(advances))
.where(at.account_head == tax_details.account_head)
.run(as_dict=True)
)
return tax_info
@@ -358,6 +381,9 @@ def get_deducted_tax(taxable_vouchers, tax_details):
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0
supp_credit_amt = 0.0
supp_jv_credit_amt = 0.0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
field = "sum(net_total)"
@@ -366,30 +392,25 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
invoice_filters.pop("apply_tds", None)
field = "sum(grand_total)"
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
if vouchers:
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
supp_jv_credit_amt = (
frappe.db.get_value(
"Journal Entry Account",
{
"parent": ("in", vouchers),
"docstatus": 1,
"party": ("in", parties),
"reference_type": ("!=", "Purchase Invoice"),
},
"sum(credit_in_account_currency)",
)
or 0.0
)
supp_jv_credit_amt = (
frappe.db.get_value(
"Journal Entry Account",
{
"parent": ("in", vouchers),
"docstatus": 1,
"party": ("in", parties),
"reference_type": ("!=", "Purchase Invoice"),
},
"sum(credit_in_account_currency)",
)
) or 0.0
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.net_total
debit_note_amount = get_debit_note_amount(
parties, tax_details.from_date, tax_details.to_date, inv.company
)
supp_credit_amt -= debit_note_amount
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
@@ -401,7 +422,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
net_total = 0
if vouchers:
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
net_total += inv.net_total
supp_credit_amt = net_total - cumulative_threshold
@@ -422,36 +446,40 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
tcs_amount = 0
invoiced_amt = 0
advance_amt = 0
# sum of debit entries made from sales invoices
invoiced_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", vouchers],
},
"sum(debit)",
if vouchers:
invoiced_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", vouchers],
},
"sum(debit)",
)
or 0.0
)
or 0.0
)
# sum of credit entries made from PE / JV with unset 'against voucher'
advance_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", adv_vouchers],
},
"sum(credit)",
if advance_amt:
advance_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", adv_vouchers],
},
"sum(credit)",
)
or 0.0
)
or 0.0
)
# sum of credit entries made from sales invoice
credit_note_amt = sum(
@@ -506,22 +534,6 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
return tds_amount
def get_debit_note_amount(suppliers, from_date, to_date, company=None):
filters = {
"supplier": ["in", suppliers],
"is_return": 1,
"docstatus": 1,
"posting_date": ["between", (from_date, to_date)],
}
fields = ["abs(sum(net_total)) as net_total"]
if company:
filters["company"] = company
return frappe.get_all("Purchase Invoice", filters, fields)[0].get("net_total") or 0.0
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
return current_amount * rate / 100

View File

@@ -52,7 +52,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(pi)
# delete invoices to avoid clashing
for d in invoices:
for d in reversed(invoices):
d.cancel()
def test_single_threshold_tds(self):
@@ -88,7 +88,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(pi.taxes_and_charges_deducted, 1000)
# delete invoices to avoid clashing
for d in invoices:
for d in reversed(invoices):
d.cancel()
def test_tax_withholding_category_checks(self):
@@ -114,7 +114,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# TDS should be applied only on 1000
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
for d in invoices:
for d in reversed(invoices):
d.cancel()
def test_cumulative_threshold_tcs(self):
@@ -148,8 +148,8 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(tcs_charged, 500)
invoices.append(si)
# delete invoices to avoid clashing
for d in invoices:
# cancel invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
def test_tds_calculation_on_net_total(self):
@@ -182,8 +182,8 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(pi1.taxes[0].tax_amount, 4000)
# delete invoices to avoid clashing
for d in invoices:
# cancel invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
def test_multi_category_single_supplier(self):
@@ -207,8 +207,50 @@ class TestTaxWithholdingCategory(unittest.TestCase):
self.assertEqual(pi1.taxes[0].tax_amount, 250)
# delete invoices to avoid clashing
for d in invoices:
# cancel invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
def test_tax_withholding_category_voucher_display(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier6", "tax_withholding_category", "Test Multi Invoice Category"
)
invoices = []
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True)
pi.apply_tds = 1
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.save()
pi.submit()
invoices.append(pi)
pi1 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=2000, do_not_save=True)
pi1.apply_tds = 1
pi1.is_return = 1
pi1.items[0].qty = -1
pi1.tax_withholding_category = "Test Multi Invoice Category"
pi1.save()
pi1.submit()
invoices.append(pi1)
pi2 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=9000, do_not_save=True)
pi2.apply_tds = 1
pi2.tax_withholding_category = "Test Multi Invoice Category"
pi2.save()
pi2.submit()
invoices.append(pi2)
pi2.load_from_db()
self.assertTrue(pi2.taxes[0].tax_amount, 1100)
self.assertTrue(pi2.tax_withheld_vouchers[0].voucher_name == pi1.name)
self.assertTrue(pi2.tax_withheld_vouchers[0].taxable_amount == pi1.net_total)
self.assertTrue(pi2.tax_withheld_vouchers[1].voucher_name == pi.name)
self.assertTrue(pi2.tax_withheld_vouchers[1].taxable_amount == pi.net_total)
# cancel invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
@@ -308,6 +350,7 @@ def create_records():
"Test TDS Supplier3",
"Test TDS Supplier4",
"Test TDS Supplier5",
"Test TDS Supplier6",
]:
if frappe.db.exists("Supplier", name):
continue
@@ -498,3 +541,22 @@ def create_tax_with_holding_category():
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Test Multi Invoice Category",
"category_name": "Test Multi Invoice Category",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 5000,
"cumulative_threshold": 10000,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()

View File

@@ -535,7 +535,11 @@ def get_accounts(root_type, companies):
):
if account.account_name not in added_accounts:
accounts.append(account)
added_accounts.append(account.account_name)
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
added_accounts.append(account_key)
return accounts

View File

@@ -280,9 +280,9 @@ def get_conditions(filters):
or filters.get("party")
or filters.get("group_by") in ["Group by Account", "Group by Party"]
):
conditions.append("posting_date >=%(from_date)s")
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
conditions.append("(posting_date <=%(to_date)s)")
if filters.get("project"):
conditions.append("project in %(project)s")

View File

@@ -155,7 +155,6 @@ def adjust_account(data, period_list, consolidated=False):
for d in data:
for period in period_list:
key = period if consolidated else period.key
d[key] = totals[d["account"]]
d["total"] = totals[d["account"]]
return data

View File

@@ -19,14 +19,19 @@ def execute(filters=None):
return _execute(filters)
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
def _execute(
filters=None,
additional_table_columns=None,
additional_query_columns=None,
additional_conditions=None,
):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns)
item_list = get_items(filters, additional_query_columns, additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@@ -97,6 +102,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row.update({"rate": d.base_net_rate, "amount": d.base_net_amount})
total_tax = 0
total_other_charges = 0
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
@@ -105,10 +111,18 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
}
)
total_tax += flt(item_tax.get("tax_amount"))
if item_tax.get("is_other_charges"):
total_other_charges += flt(item_tax.get("tax_amount"))
else:
total_tax += flt(item_tax.get("tax_amount"))
row.update(
{"total_tax": total_tax, "total": d.base_net_amount + total_tax, "currency": company_currency}
{
"total_tax": total_tax,
"total_other_charges": total_other_charges,
"total": d.base_net_amount + total_tax,
"currency": company_currency,
}
)
if filters.get("group_by"):
@@ -319,7 +333,7 @@ def get_columns(additional_table_columns, filters):
return columns
def get_conditions(filters):
def get_conditions(filters, additional_conditions=None):
conditions = ""
for opts in (
@@ -332,6 +346,9 @@ def get_conditions(filters):
if filters.get(opts[0]):
conditions += opts[1]
if additional_conditions:
conditions += additional_conditions
if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
@@ -367,8 +384,8 @@ def get_group_by_conditions(filters, doctype):
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions)
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
@@ -477,7 +494,7 @@ def get_tax_accounts(
tax_details = frappe.db.sql(
"""
select
name, parent, description, item_wise_tax_detail,
name, parent, description, item_wise_tax_detail, account_head,
charge_type, {add_deduct_tax}, base_tax_amount_after_discount_amount
from `tab%s`
where
@@ -493,11 +510,22 @@ def get_tax_accounts(
tuple([doctype] + list(invoice_item_row)),
)
account_doctype = frappe.qb.DocType("Account")
query = (
frappe.qb.from_(account_doctype)
.select(account_doctype.name)
.where((account_doctype.account_type == "Tax"))
)
tax_accounts = query.run()
for (
name,
parent,
description,
item_wise_tax_detail,
account_head,
charge_type,
add_deduct_tax,
tax_amount,
@@ -540,7 +568,11 @@ def get_tax_accounts(
)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
{"tax_rate": tax_rate, "tax_amount": tax_value}
{
"tax_rate": tax_rate,
"tax_amount": tax_value,
"is_other_charges": 0 if tuple([account_head]) in tax_accounts else 1,
}
)
except ValueError:
@@ -583,6 +615,13 @@ def get_tax_accounts(
"options": "currency",
"width": 100,
},
{
"label": _("Total Other Charges"),
"fieldname": "total_other_charges",
"fieldtype": "Currency",
"options": "currency",
"width": 100,
},
{
"label": _("Total"),
"fieldname": "total",

View File

@@ -370,7 +370,7 @@ def get_conditions(filters):
where parent=`tabSales Invoice`.name
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment")
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
conditions += get_sales_invoice_item_field_condition("cost_center")
conditions += get_sales_invoice_item_field_condition("warehouse")
conditions += get_sales_invoice_item_field_condition("brand")

View File

@@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type):
query_filters = {
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
"project": filters.project,
@@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type):
where
company=%(company)s
{additional_conditions}
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
and account in (select name from `tabAccount` where report_type=%(report_type)s)
and is_cancelled = 0
group by account""".format(

View File

@@ -106,12 +106,17 @@ def get_opening_balances(filters):
where company=%(company)s
and is_cancelled=0
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
{account_filter}
group by party""".format(
account_filter=account_filter
),
{"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type},
{
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"party_type": filters.party_type,
},
as_dict=True,
)

View File

@@ -818,6 +818,31 @@ def get_held_invoices(party_type, party):
return held_invoices
def remove_return_pos_invoices(party_type, party, invoice_list):
if invoice_list:
if party_type == "Customer":
sinv = frappe.qb.DocType("Sales Invoice")
return_pos = (
frappe.qb.from_(sinv)
.select(sinv.name)
.where((sinv.is_pos == 1) & (sinv.docstatus == 1) & (sinv.is_return == 1))
.run()
)
if return_pos:
return_pos = [x[0] for x in return_pos]
else:
return invoice_list
# remove pos return invoices from invoice_list
for idx, inv in enumerate(invoice_list, 0):
if inv.voucher_no in return_pos:
del invoice_list[idx]
return invoice_list
def get_outstanding_invoices(party_type, party, account, condition=None, filters=None):
outstanding_invoices = []
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
@@ -868,6 +893,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
as_dict=True,
)
invoice_list = remove_return_pos_invoices(party_type, party, invoice_list)
payment_entries = frappe.db.sql(
"""
select against_voucher_type, against_voucher,

View File

@@ -819,7 +819,9 @@ class Asset(AccountsController):
def update_maintenance_status():
assets = frappe.get_all("Asset", filters={"docstatus": 1, "maintenance_required": 1})
assets = frappe.get_all(
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
)
for asset in assets:
asset = frappe.get_doc("Asset", asset.name)

View File

@@ -7,7 +7,7 @@ import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, update_maintenance_status
from erpnext.assets.doctype.asset.depreciation import (
post_depreciation_entries,
restore_asset,
@@ -238,6 +238,34 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_with_maintenance_required_status_after_sale(self):
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-06-06",
purchase_date="2020-01-01",
expected_value_after_useful_life=10000,
total_number_of_depreciations=3,
frequency_of_depreciation=10,
maintenance_required=1,
depreciation_start_date="2020-12-31",
submit=1,
)
post_depreciation_entries(date="2021-01-01")
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer"
si.due_date = nowdate()
si.get("items")[0].rate = 25000
si.insert()
si.submit()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
update_maintenance_status()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
def test_expense_head(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location"
@@ -1353,6 +1381,7 @@ def create_asset(**args):
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
"gross_purchase_amount": args.gross_purchase_amount or 100000,
"purchase_receipt_amount": args.purchase_receipt_amount or 100000,
"maintenance_required": args.maintenance_required or 0,
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"available_for_use_date": args.available_for_use_date or "2020-06-06",
"location": args.location or "Test Location",

View File

@@ -439,7 +439,6 @@
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
"print_hide": 1
},
@@ -1170,7 +1169,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-04-26 12:16:38.694276",
"modified": "2022-09-16 17:45:04.954055",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
from erpnext.accounts.party import get_party_account_currency
from erpnext.accounts.party import get_party_account, get_party_account_currency
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
from erpnext.controllers.buying_controller import BuyingController
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -323,6 +323,7 @@ class PurchaseOrder(BuyingController):
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
super(PurchaseOrder, self).on_cancel()
if self.is_against_so():
@@ -532,6 +533,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
target.set_advances()
target.set_payment_schedule()
target.credit_to = get_party_account("Supplier", source.supplier, source.company)
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)

View File

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

View File

@@ -193,16 +193,16 @@ class BuyingController(StockController, Subcontracting):
if self.meta.get_field("base_in_words"):
if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled():
amount = self.base_rounded_total
amount = abs(self.base_rounded_total)
else:
amount = self.base_grand_total
amount = abs(self.base_grand_total)
self.base_in_words = money_in_words(amount, self.company_currency)
if self.meta.get_field("in_words"):
if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled():
amount = self.rounded_total
amount = abs(self.rounded_total)
else:
amount = self.grand_total
amount = abs(self.grand_total)
self.in_words = money_in_words(amount, self.currency)
@@ -303,7 +303,11 @@ class BuyingController(StockController, Subcontracting):
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1),
d.precision("rate"),
)
if self.is_internal_transfer():
if rate != d.rate:

View File

@@ -439,11 +439,17 @@ class SellingController(StockController):
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
incoming_rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("incoming_rate"),
)
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(

View File

@@ -139,13 +139,15 @@ class StockController(AccountsController):
warehouse_with_no_account = []
precision = self.get_debit_field_precision()
for item_row in voucher_details:
sle_list = sle_map.get(item_row.name)
sle_rounding_diff = 0.0
if sle_list:
for sle in sle_list:
if warehouse_account.get(sle.warehouse):
# from warehouse account
sle_rounding_diff += flt(sle.stock_value_difference)
self.check_expense_account(item_row)
# expense account/ target_warehouse / source_warehouse
@@ -188,6 +190,46 @@ class StockController(AccountsController):
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
warehouse_asset_account = ""
if self.get("is_internal_customer"):
warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"]
elif self.get("is_internal_supplier"):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
gl_list.append(
self.get_gl_dict(
{
"account": expense_account,
"against": warehouse_asset_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"debit": sle_rounding_diff,
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
warehouse_account[sle.warehouse]["account_currency"],
item=item_row,
)
)
gl_list.append(
self.get_gl_dict(
{
"account": warehouse_asset_account,
"against": expense_account,
"cost_center": item_row.cost_center,
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"credit": sle_rounding_diff,
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
item=item_row,
)
)
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):

View File

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

View File

@@ -11,9 +11,12 @@ frappe.ui.form.on('Course Scheduling Tool', {
},
refresh(frm) {
frm.disable_save();
frm.trigger("render_days");
frm.page.set_primary_action(__('Schedule Course'), () => {
frm.call('schedule_course')
frappe.dom.freeze(__("Scheduling..."));
frm.call('schedule_course', { days: frm.days.get_checked_options() })
.then(r => {
frappe.dom.unfreeze();
if (!r.message) {
frappe.throw(__('There were errors creating Course Schedule'));
}
@@ -40,5 +43,60 @@ frappe.ui.form.on('Course Scheduling Tool', {
}
});
});
},
render_days: function(frm) {
const days_html = $('<div class="days-editor">').appendTo(
frm.fields_dict.days_html.wrapper
);
if (!frm.days) {
frm.days = frappe.ui.form.make_control({
parent: days_html,
df: {
fieldname: "days",
fieldtype: "MultiCheck",
select_all: true,
columns: 4,
options: [
{
label: __("Monday"),
value: "Monday",
checked: 0,
},
{
label: __("Tuesday"),
value: "Tuesday",
checked: 0,
},
{
label: __("Wednesday"),
value: "Wednesday",
checked: 0,
},
{
label: __("Thursday"),
value: "Thursday",
checked: 0,
},
{
label: __("Friday"),
value: "Friday",
checked: 0,
},
{
label: __("Saturday"),
value: "Saturday",
checked: 0,
},
{
label: __("Sunday"),
value: "Sunday",
checked: 0,
},
],
},
render_input: true,
});
}
}
});

View File

@@ -1,661 +1,171 @@
{
"allow_copy": 1,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2015-09-23 15:37:38.108475",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"actions": [],
"allow_copy": 1,
"creation": "2015-09-23 15:37:38.108475",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"student_group",
"course",
"program",
"column_break_3",
"academic_year",
"academic_term",
"section_break_6",
"instructor",
"instructor_name",
"column_break_9",
"room",
"section_break_7",
"days_html",
"section_break_14",
"from_time",
"course_start_date",
"column_break_15",
"to_time",
"course_end_date",
"reschedule"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Student Group",
"length": 0,
"no_copy": 0,
"options": "Student Group",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "student_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Student Group",
"options": "Student Group",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "course",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "Course",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "program",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Program",
"length": 0,
"no_copy": 0,
"options": "Program",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "program",
"fieldtype": "Link",
"label": "Program",
"options": "Program",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_year",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Academic Year",
"length": 0,
"no_copy": 0,
"options": "Academic Year",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "academic_year",
"fieldtype": "Link",
"label": "Academic Year",
"options": "Academic Year",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_term",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Academic Term",
"length": 0,
"no_copy": 0,
"options": "Academic Term",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "academic_term",
"fieldtype": "Link",
"label": "Academic Term",
"options": "Academic Term",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "instructor",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Instructor",
"length": 0,
"no_copy": 0,
"options": "Instructor",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "instructor",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Instructor",
"options": "Instructor",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "instructor.instructor_name",
"fieldname": "instructor_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Instructor Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "instructor_name",
"fieldtype": "Read Only",
"label": "Instructor Name",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Room",
"length": 0,
"no_copy": 0,
"options": "Room",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "room",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Room",
"options": "Room",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "from_time",
"fieldtype": "Time",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "From Time",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "from_time",
"fieldtype": "Time",
"label": "From Time",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "course_start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Course Start Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "course_start_date",
"fieldtype": "Date",
"label": "Course Start Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "day",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Day",
"length": 0,
"no_copy": 0,
"options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "reschedule",
"fieldtype": "Check",
"label": "Reschedule"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reschedule",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reschedule",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_15",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_15",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "to_time",
"fieldtype": "Time",
"label": "To TIme",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_time",
"fieldtype": "Time",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To TIme",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "course_end_date",
"fieldtype": "Date",
"label": "Course End Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "course_end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Course End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "days_html",
"fieldtype": "HTML",
"label": "Days HTML"
},
{
"fieldname": "section_break_14",
"fieldtype": "Section Break"
}
],
"has_web_view": 0,
"hide_heading": 1,
"hide_toolbar": 1,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-05-16 22:43:29.363798",
"modified_by": "Administrator",
"module": "Education",
"name": "Course Scheduling Tool",
"name_case": "",
"owner": "Administrator",
],
"hide_toolbar": 1,
"issingle": 1,
"links": [],
"modified": "2022-10-01 17:08:07.180557",
"modified_by": "Administrator",
"module": "Education",
"name": "Course Scheduling Tool",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Academics User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"create": 1,
"read": 1,
"role": "Academics User",
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"restrict_to_domain": "Education",
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -14,7 +14,7 @@ from erpnext.education.utils import OverlapError
class CourseSchedulingTool(Document):
@frappe.whitelist()
def schedule_course(self):
def schedule_course(self, days):
"""Creates course schedules as per specified parameters"""
course_schedules = []
@@ -22,7 +22,7 @@ class CourseSchedulingTool(Document):
rescheduled = []
reschedule_errors = []
self.validate_mandatory()
self.validate_mandatory(days)
self.validate_date()
self.instructor_name = frappe.db.get_value("Instructor", self.instructor, "instructor_name")
@@ -34,24 +34,22 @@ class CourseSchedulingTool(Document):
self.course = course
if self.reschedule:
rescheduled, reschedule_errors = self.delete_course_schedule(rescheduled, reschedule_errors)
rescheduled, reschedule_errors = self.delete_course_schedule(
rescheduled, reschedule_errors, days
)
date = self.course_start_date
while date < self.course_end_date:
if self.day == calendar.day_name[getdate(date).weekday()]:
if calendar.day_name[getdate(date).weekday()] in days:
course_schedule = self.make_course_schedule(date)
try:
print("pass")
course_schedule.save()
except OverlapError:
print("fail")
course_schedules_errors.append(date)
else:
course_schedules.append(course_schedule)
date = add_days(date, 7)
else:
date = add_days(date, 1)
date = add_days(date, 1)
return dict(
course_schedules=course_schedules,
@@ -60,8 +58,10 @@ class CourseSchedulingTool(Document):
reschedule_errors=reschedule_errors,
)
def validate_mandatory(self):
def validate_mandatory(self, days):
"""Validates all mandatory fields"""
if not days:
frappe.throw(_("Please select at least one day to schedule the course."))
fields = [
"course",
@@ -71,7 +71,6 @@ class CourseSchedulingTool(Document):
"to_time",
"course_start_date",
"course_end_date",
"day",
]
for d in fields:
if not self.get(d):
@@ -82,9 +81,8 @@ class CourseSchedulingTool(Document):
if self.course_start_date > self.course_end_date:
frappe.throw(_("Course Start Date cannot be greater than Course End Date."))
def delete_course_schedule(self, rescheduled, reschedule_errors):
def delete_course_schedule(self, rescheduled, reschedule_errors, days):
"""Delete all course schedule within the Date range and specified filters"""
schedules = frappe.get_list(
"Course Schedule",
fields=["name", "schedule_date"],
@@ -98,7 +96,7 @@ class CourseSchedulingTool(Document):
for d in schedules:
try:
if self.day == calendar.day_name[getdate(d.schedule_date).weekday()]:
if calendar.day_name[getdate(d.schedule_date).weekday()] in days:
frappe.delete_doc("Course Schedule", d.name)
rescheduled.append(d.name)
except Exception:
@@ -108,7 +106,6 @@ class CourseSchedulingTool(Document):
def make_course_schedule(self, date):
"""Makes a new Course Schedule.
:param date: Date on which Course Schedule will be created."""
course_schedule = frappe.new_doc("Course Schedule")
course_schedule.student_group = self.student_group
course_schedule.course = self.course

View File

@@ -334,8 +334,6 @@ has_website_permission = {
"Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission",
}
dump_report_map = "erpnext.startup.report_data_map.data_map"
before_tests = "erpnext.setup.utils.before_tests"
standard_queries = {
@@ -478,7 +476,6 @@ scheduler_events = {
],
"hourly": [
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
"erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
"erpnext.projects.doctype.project.project.hourly_reminder",
@@ -487,6 +484,7 @@ scheduler_events = {
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
],
"hourly_long": [
"erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
],

View File

@@ -253,10 +253,12 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
if joining_date and joining_date.month == month_map[month]:
if joining_date and joining_date.year == today.year and joining_date.month == month_map[month]:
start_day = joining_date.day
if relieving_date and relieving_date.month == month_map[month]:
if (
relieving_date and relieving_date.year == today.year and relieving_date.month == month_map[month]
):
end_day = relieving_date.day + 1
dates_of_month = [

View File

@@ -13,6 +13,8 @@ frappe.listview_settings['Attendance'] = {
onload: function(list_view) {
let me = this;
const months = moment.months();
const curMonth = moment().format("MMMM");
months.splice(months.indexOf(curMonth) + 1);
list_view.page.add_inner_button(__("Mark Attendance"), function() {
let dialog = new frappe.ui.Dialog({
title: __("Mark Attendance"),

View File

@@ -19,7 +19,7 @@ from erpnext.hr.doctype.attendance.attendance import (
mark_attendance,
)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
from erpnext.hr.tests.test_utils import get_first_sunday
test_records = frappe.get_test_records("Attendance")

View File

@@ -52,7 +52,7 @@ frappe.ui.form.on("Leave Application", {
make_dashboard: function(frm) {
var leave_details;
let lwps;
if (frm.doc.employee && frm.doc.from_date) {
if (frm.doc.employee) {
frappe.call({
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
async: false,

View File

@@ -33,6 +33,7 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
create_assignment_for_multiple_employees,
)
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,
@@ -1105,23 +1106,6 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
allocate_leave.submit()
def get_first_sunday(holiday_list, for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
""",
(holiday_list, month_start_date, month_end_date),
)[0][0]
return first_sunday
def make_policy_assignment(employee, leave_type, leave_period):
frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
frappe.get_doc(

View File

@@ -8,11 +8,11 @@ frappe.ui.form.on(cur_frm.doctype, {
};
});
},
onload: function(frm){
if(frm.doc.__islocal){
if(frm.doctype == "Employee Promotion"){
onload: function(frm) {
if (frm.doc.__islocal && !frm.doc.amended_from) {
if (frm.doctype == "Employee Promotion") {
frm.doc.promotion_details = [];
}else if (frm.doctype == "Employee Transfer") {
} else if (frm.doctype == "Employee Transfer") {
frm.doc.transfer_details = [];
}
}
@@ -106,12 +106,12 @@ var render_dynamic_field = function(d, fieldtype, options, fieldname) {
var add_to_details = function(frm, d, table) {
let data = d.data;
if(data.fieldname){
if(validate_duplicate(frm, table, data.fieldname)){
if (data.fieldname) {
if (validate_duplicate(frm, table, data.fieldname)) {
frappe.show_alert({message:__("Property already added"), indicator:'orange'});
return false;
}
if(data.current == data.new){
if (data.current == data.new) {
frappe.show_alert({message:__("Nothing to change"), indicator:'orange'});
d.get_primary_btn().attr('disabled', false);
return false;
@@ -123,12 +123,14 @@ var add_to_details = function(frm, d, table) {
new: data.new
});
frm.refresh_field(table);
frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide();
d.fields_dict.field_html.$wrapper.html("");
d.set_value("property", "");
d.set_value('current', "");
frappe.show_alert({message:__("Added to details"),indicator:'green'});
d.data = {};
}else {
} else {
frappe.show_alert({message:__("Value missing"),indicator:'red'});
}
};

View File

@@ -9,13 +9,11 @@ from frappe.utils import add_days, add_months, flt, get_year_ending, get_year_st
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_application.test_leave_application import (
get_first_sunday,
make_allocation_record,
)
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import execute
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,

View File

@@ -22,9 +22,9 @@ def execute(filters=None):
def get_columns(leave_types):
columns = [
_("Employee") + ":Link.Employee:150",
_("Employee") + ":Link/Employee:150",
_("Employee Name") + "::200",
_("Department") + "::150",
_("Department") + ":Link/Department:150",
]
for leave_type in leave_types:

View File

@@ -9,12 +9,10 @@ from frappe.utils import add_days, flt, get_year_ending, get_year_start, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_application.test_leave_application import (
get_first_sunday,
make_allocation_record,
)
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary import execute
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,

View File

@@ -181,7 +181,6 @@ def add_data(
total_l += 1
elif status == "Half Day":
total_p += 0.5
total_a += 0.5
total_l += 0.5
elif not status:
total_um += 1

View File

@@ -0,0 +1,19 @@
import frappe
from frappe.utils import get_first_day, get_last_day, getdate
def get_first_sunday(holiday_list="Salary Slip Test Holiday List", for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
""",
(holiday_list, month_start_date, month_end_date),
)[0][0]
return first_sunday

View File

@@ -17,6 +17,7 @@
"posting_date",
"status",
"repay_from_salary",
"manually_update_paid_amount_in_salary_slip",
"section_break_8",
"loan_type",
"loan_amount",
@@ -51,10 +52,10 @@
"refund_amount",
"debit_adjustment_amount",
"credit_adjustment_amount",
"is_npa",
"column_break_19",
"total_interest_payable",
"total_amount_paid",
"is_npa",
"amended_from"
],
"fields": [
@@ -410,16 +411,23 @@
"fieldname": "is_npa",
"fieldtype": "Check",
"label": "Is NPA"
},
{
"allow_on_submit": 1,
"default": "0",
"depends_on": "repay_from_salary",
"fieldname": "manually_update_paid_amount_in_salary_slip",
"fieldtype": "Check",
"label": "Manually Update Paid Amount in Salary Slip"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-06-30 12:04:13.728880",
"modified": "2022-09-13 02:05:25.017190",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -445,6 +453,5 @@
"search_fields": "posting_date",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -236,7 +236,7 @@ def get_term_loans(date, term_loan=None, loan_type=None):
AND l.is_term_loan =1
AND rs.payment_date <= %s
AND rs.is_accrued=0 {0}
AND rs.interest_amount > 0
AND rs.principal_amount > 0
AND l.status = 'Disbursed'
ORDER BY rs.payment_date""".format(
condition

View File

@@ -520,6 +520,8 @@ def get_accrued_interest_entries(against_loan, posting_date=None):
if not posting_date:
posting_date = getdate()
precision = cint(frappe.db.get_default("currency_precision")) or 2
unpaid_accrued_entries = frappe.db.sql(
"""
SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount,
@@ -540,6 +542,13 @@ def get_accrued_interest_entries(against_loan, posting_date=None):
as_dict=1,
)
# Skip entries with zero interest amount & payable principal amount
unpaid_accrued_entries = [
d
for d in unpaid_accrued_entries
if flt(d.interest_amount, precision) > 0 or flt(d.payable_principal_amount, precision) > 0
]
return unpaid_accrued_entries
@@ -734,6 +743,7 @@ def get_amounts(amounts, against_loan, posting_date):
)
amounts["pending_accrual_entries"] = pending_accrual_entries
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision)
if final_due_date:
amounts["due_date"] = final_due_date

View File

@@ -57,7 +57,7 @@ def process_loan_interest_accrual_for_demand_loans(
def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None):
if not term_loan_accrual_pending(posting_date or nowdate()):
if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan):
return
loan_process = frappe.new_doc("Process Loan Interest Accrual")
@@ -71,9 +71,12 @@ def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=No
return loan_process.name
def term_loan_accrual_pending(date):
pending_accrual = frappe.db.get_value(
"Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0}
)
def term_loan_accrual_pending(date, loan=None):
filters = {"payment_date": ("<=", date), "is_accrued": 0}
if loan:
filters.update({"parent": loan})
pending_accrual = frappe.db.get_value("Repayment Schedule", filters)
return pending_accrual

View File

@@ -64,7 +64,7 @@
"fieldname": "total_payment",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total Payment",
"label": "Paid Amount",
"options": "Company:company:default_currency"
},
{
@@ -87,7 +87,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-01-31 14:50:14.823213",
"modified": "2022-08-29 08:50:39.030296",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Salary Slip Loan",
@@ -96,6 +96,5 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

@@ -162,5 +162,54 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
},
{
"operations": [
{
"operation": "_Test Operation 1",
"description": "_Test",
"workstation": "_Test Workstation 1",
"hour_rate": 100,
"time_in_mins": 60,
"operating_cost": 100
}
],
"items": [
{
"amount": 5000.0,
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
"qty": 1.0,
"rate": 5000.0,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC",
"include_item_in_manufacturing": 1
},
{
"amount": 3000.0,
"bom_no": "BOM-_Test Item Home Desktop Manufactured-001",
"doctype": "BOM Item",
"item_code": "_Test Item Home Desktop Manufactured",
"parentfield": "items",
"qty": 3.0,
"rate": 1000.0,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC",
"include_item_in_manufacturing": 1
}
],
"docstatus": 1,
"doctype": "BOM",
"is_active": 1,
"is_default": 1,
"currency": "USD",
"conversion_rate": 60,
"company": "_Test Company",
"item": "_Test FG Item 3",
"quantity": 1.0,
"with_operations": 1
}
]

View File

@@ -11,6 +11,7 @@
"col_break1",
"workstation",
"time_in_mins",
"fixed_time",
"costing_section",
"hour_rate",
"base_hour_rate",
@@ -80,6 +81,14 @@
"oldfieldtype": "Currency",
"reqd": 1
},
{
"default": "0",
"description": "Operation time does not depend on quantity to produce",
"fieldname": "fixed_time",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Fixed Time"
},
{
"fieldname": "operating_cost",
"fieldtype": "Currency",
@@ -177,7 +186,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-04-08 01:18:33.547481",
"modified": "2022-08-22 01:18:33.547481",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
@@ -185,4 +194,4 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -8,6 +8,7 @@ import json
import frappe
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import (
add_days,
ceil,
@@ -20,6 +21,7 @@ from frappe.utils import (
nowdate,
)
from frappe.utils.csvutils import build_csv_response
from pypika.terms import ExistsCriterion
from six import iteritems
from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no
@@ -100,39 +102,46 @@ class ProductionPlan(Document):
@frappe.whitelist()
def get_pending_material_requests(self):
"""Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
bom = frappe.qb.DocType("BOM")
mr = frappe.qb.DocType("Material Request")
mr_item = frappe.qb.DocType("Material Request Item")
pending_mr_query = (
frappe.qb.from_(mr)
.from_(mr_item)
.select(mr.name, mr.transaction_date)
.distinct()
.where(
(mr_item.parent == mr.name)
& (mr.material_request_type == "Manufacture")
& (mr.docstatus == 1)
& (mr.status != "Stopped")
& (mr.company == self.company)
& (mr_item.qty > IfNull(mr_item.ordered_qty, 0))
& (
ExistsCriterion(
frappe.qb.from_(bom)
.select(bom.name)
.where((bom.item == mr_item.item_code) & (bom.is_active == 1))
)
)
)
)
if self.from_date:
mr_filter += " and mr.transaction_date >= %(from_date)s"
pending_mr_query = pending_mr_query.where(mr.transaction_date >= self.from_date)
if self.to_date:
mr_filter += " and mr.transaction_date <= %(to_date)s"
pending_mr_query = pending_mr_query.where(mr.transaction_date <= self.to_date)
if self.warehouse:
mr_filter += " and mr_item.warehouse = %(warehouse)s"
pending_mr_query = pending_mr_query.where(mr_item.warehouse == self.warehouse)
if self.item_code:
item_filter += " and mr_item.item_code = %(item)s"
pending_mr_query = pending_mr_query.where(mr_item.item_code == self.item_code)
pending_mr = frappe.db.sql(
"""
select distinct mr.name, mr.transaction_date
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
and mr.material_request_type = "Manufacture"
and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
""".format(
mr_filter, item_filter
),
{
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item": self.item_code,
"company": self.company,
},
as_dict=1,
)
pending_mr = pending_mr_query.run(as_dict=True)
self.add_mr_in_table(pending_mr)
@@ -160,16 +169,17 @@ class ProductionPlan(Document):
so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)]
return so_mr_list
def get_bom_item(self):
def get_bom_item_condition(self):
"""Check if Item or if its Template has a BOM."""
bom_item = None
bom_item_condition = None
has_bom = frappe.db.exists({"doctype": "BOM", "item": self.item_code, "docstatus": 1})
if not has_bom:
bom = frappe.qb.DocType("BOM")
template_item = frappe.db.get_value("Item", self.item_code, ["variant_of"])
bom_item = (
"bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
)
return bom_item
bom_item_condition = bom.item == template_item or None
return bom_item_condition
def get_so_items(self):
# Check for empty table or empty rows
@@ -178,46 +188,75 @@ class ProductionPlan(Document):
so_list = self.get_so_mr_list("sales_order", "sales_orders")
item_condition = ""
bom_item = "bom.item = so_item.item_code"
if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
bom = frappe.qb.DocType("BOM")
so_item = frappe.qb.DocType("Sales Order Item")
items = frappe.db.sql(
"""
select
distinct parent, item_code, warehouse,
(qty - work_order_qty) * conversion_factor as pending_qty,
description, name
from
`tabSales Order Item` so_item
where
parent in (%s) and docstatus = 1 and qty > work_order_qty
and exists (select name from `tabBOM` bom where %s
and bom.is_active = 1) %s"""
% (", ".join(["%s"] * len(so_list)), bom_item, item_condition),
tuple(so_list),
as_dict=1,
items_subquery = frappe.qb.from_(bom).select(bom.name).where(bom.is_active == 1)
items_query = (
frappe.qb.from_(so_item)
.select(
so_item.parent,
so_item.item_code,
so_item.warehouse,
(
(so_item.qty - so_item.work_order_qty - so_item.delivered_qty) * so_item.conversion_factor
).as_("pending_qty"),
so_item.description,
so_item.name,
)
.distinct()
.where(
(so_item.parent.isin(so_list))
& (so_item.docstatus == 1)
& (so_item.qty > so_item.work_order_qty)
)
)
if self.item_code and frappe.db.exists("Item", self.item_code):
items_query = items_query.where(so_item.item_code == self.item_code)
items_subquery = items_subquery.where(
self.get_bom_item_condition() or bom.item == so_item.item_code
)
items_query = items_query.where(ExistsCriterion(items_subquery))
items = items_query.run(as_dict=True)
pi = frappe.qb.DocType("Packed Item")
packed_items_query = (
frappe.qb.from_(so_item)
.from_(pi)
.select(
pi.parent,
pi.item_code,
pi.warehouse.as_("warehouse"),
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty).as_("pending_qty"),
pi.parent_item,
pi.description,
so_item.name,
)
.distinct()
.where(
(so_item.parent == pi.parent)
& (so_item.docstatus == 1)
& (pi.parent_item == so_item.item_code)
& (so_item.parent.isin(so_list))
& (so_item.qty > so_item.work_order_qty)
& (
ExistsCriterion(
frappe.qb.from_(bom)
.select(bom.name)
.where((bom.item == pi.item_code) & (bom.is_active == 1))
)
)
)
)
if self.item_code:
item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code))
packed_items_query = packed_items_query.where(so_item.item_code == self.item_code)
packed_items = frappe.db.sql(
"""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
as pending_qty, pi.parent_item, pi.description, so_item.name
from `tabSales Order Item` so_item, `tabPacked Item` pi
where so_item.parent = pi.parent and so_item.docstatus = 1
and pi.parent_item = so_item.item_code
and so_item.parent in (%s) and so_item.qty > so_item.work_order_qty
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1) %s"""
% (", ".join(["%s"] * len(so_list)), item_condition),
tuple(so_list),
as_dict=1,
)
packed_items = packed_items_query.run(as_dict=True)
self.add_items(items + packed_items)
self.calculate_total_planned_qty()
@@ -233,22 +272,39 @@ class ProductionPlan(Document):
mr_list = self.get_so_mr_list("material_request", "material_requests")
item_condition = ""
if self.item_code:
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
bom = frappe.qb.DocType("BOM")
mr_item = frappe.qb.DocType("Material Request Item")
items = frappe.db.sql(
"""select distinct parent, name, item_code, warehouse, description,
(qty - ordered_qty) * conversion_factor as pending_qty
from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1) %s"""
% (", ".join(["%s"] * len(mr_list)), item_condition),
tuple(mr_list),
as_dict=1,
items_query = (
frappe.qb.from_(mr_item)
.select(
mr_item.parent,
mr_item.name,
mr_item.item_code,
mr_item.warehouse,
mr_item.description,
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
)
.distinct()
.where(
(mr_item.parent.isin(mr_list))
& (mr_item.docstatus == 1)
& (mr_item.qty > mr_item.ordered_qty)
& (
ExistsCriterion(
frappe.qb.from_(bom)
.select(bom.name)
.where((bom.item == mr_item.item_code) & (bom.is_active == 1))
)
)
)
)
if self.item_code:
items_query = items_query.where(mr_item.item_code == self.item_code)
items = items_query.run(as_dict=True)
self.add_items(items)
self.calculate_total_planned_qty()
@@ -754,29 +810,46 @@ def download_raw_materials(doc, warehouses=None):
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
for d in frappe.db.sql(
"""select bei.item_code, item.default_bom as bom,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name,
bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse,
item.default_material_request_type, item.min_order_qty, item_default.default_warehouse,
item.purchase_uom, item_uom.conversion_factor, item.safety_stock
from
`tabBOM Explosion Item` bei
JOIN `tabBOM` bom ON bom.name = bei.parent
JOIN `tabItem` item ON item.name = bei.item_code
LEFT JOIN `tabItem Default` item_default
ON item_default.parent = item.name and item_default.company=%s
LEFT JOIN `tabUOM Conversion Detail` item_uom
ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom
where
bei.docstatus < 2
and bom.name=%s and item.is_stock_item in (1, {0})
group by bei.item_code, bei.stock_uom""".format(
0 if include_non_stock_items else 1
),
(planned_qty, company, bom_no),
as_dict=1,
):
bei = frappe.qb.DocType("BOM Explosion Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
item_uom = frappe.qb.DocType("UOM Conversion Detail")
data = (
frappe.qb.from_(bei)
.join(bom)
.on(bom.name == bei.parent)
.join(item)
.on(item.name == bei.item_code)
.left_join(item_default)
.on((item_default.parent == item.name) & (item_default.company == company))
.left_join(item_uom)
.on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
.select(
(IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"),
item.item_name,
item.name.as_("item_code"),
bei.description,
bei.stock_uom,
item.min_order_qty,
bei.source_warehouse,
item.default_material_request_type,
item.min_order_qty,
item_default.default_warehouse,
item.purchase_uom,
item_uom.conversion_factor,
item.safety_stock,
)
.where(
(bei.docstatus < 2)
& (bom.name == bom_no)
& (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
)
.groupby(bei.item_code, bei.stock_uom)
).run(as_dict=True)
for d in data:
if not d.conversion_factor and d.purchase_uom:
d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom)
item_details.setdefault(d.get("item_code"), d)
@@ -801,33 +874,47 @@ def get_subitems(
parent_qty,
planned_qty=1,
):
items = frappe.db.sql(
"""
SELECT
bom_item.item_code, default_material_request_type, item.item_name,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
item.default_bom as default_bom, bom_item.description as description,
bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock,
item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor
FROM
`tabBOM Item` bom_item
JOIN `tabBOM` bom ON bom.name = bom_item.parent
JOIN tabItem item ON bom_item.item_code = item.name
LEFT JOIN `tabItem Default` item_default
ON item.name = item_default.parent and item_default.company = %(company)s
LEFT JOIN `tabUOM Conversion Detail` item_uom
ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom
where
bom.name = %(bom)s
and bom_item.docstatus < 2
and item.is_stock_item in (1, {0})
group by bom_item.item_code""".format(
0 if include_non_stock_items else 1
),
{"bom": bom_no, "parent_qty": parent_qty, "planned_qty": planned_qty, "company": company},
as_dict=1,
)
bom_item = frappe.qb.DocType("BOM Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
item_uom = frappe.qb.DocType("UOM Conversion Detail")
items = (
frappe.qb.from_(bom_item)
.join(bom)
.on(bom.name == bom_item.parent)
.join(item)
.on(bom_item.item_code == item.name)
.left_join(item_default)
.on((item.name == item_default.parent) & (item_default.company == company))
.left_join(item_uom)
.on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
.select(
bom_item.item_code,
item.default_material_request_type,
item.item_name,
IfNull(parent_qty * Sum(bom_item.stock_qty / IfNull(bom.quantity, 1)) * planned_qty, 0).as_(
"qty"
),
item.is_sub_contracted_item.as_("is_sub_contracted"),
bom_item.source_warehouse,
item.default_bom.as_("default_bom"),
bom_item.description.as_("description"),
bom_item.stock_uom.as_("stock_uom"),
item.min_order_qty.as_("min_order_qty"),
item.safety_stock.as_("safety_stock"),
item_default.default_warehouse,
item.purchase_uom,
item_uom.conversion_factor,
)
.where(
(bom.name == bom_no)
& (bom_item.docstatus < 2)
& (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
)
.groupby(bom_item.item_code)
).run(as_dict=True)
for d in items:
if not data.get("include_exploded_items") or not d.default_bom:
@@ -915,48 +1002,69 @@ def get_material_request_items(
def get_sales_orders(self):
so_filter = item_filter = ""
bom_item = "bom.item = so_item.item_code"
bom = frappe.qb.DocType("BOM")
pi = frappe.qb.DocType("Packed Item")
so = frappe.qb.DocType("Sales Order")
so_item = frappe.qb.DocType("Sales Order Item")
open_so_subquery1 = frappe.qb.from_(bom).select(bom.name).where(bom.is_active == 1)
open_so_subquery2 = (
frappe.qb.from_(pi)
.select(pi.name)
.where(
(pi.parent == so.name)
& (pi.parent_item == so_item.item_code)
& (
ExistsCriterion(
frappe.qb.from_(bom).select(bom.name).where((bom.item == pi.item_code) & (bom.is_active == 1))
)
)
)
)
open_so_query = (
frappe.qb.from_(so)
.from_(so_item)
.select(so.name, so.transaction_date, so.customer, so.base_grand_total)
.distinct()
.where(
(so_item.parent == so.name)
& (so.docstatus == 1)
& (so.status.notin(["Stopped", "Closed"]))
& (so.company == self.company)
& (so_item.qty > so_item.work_order_qty)
)
)
date_field_mapper = {
"from_date": (">=", "so.transaction_date"),
"to_date": ("<=", "so.transaction_date"),
"from_delivery_date": (">=", "so_item.delivery_date"),
"to_delivery_date": ("<=", "so_item.delivery_date"),
"from_date": self.from_date >= so.transaction_date,
"to_date": self.to_date <= so.transaction_date,
"from_delivery_date": self.from_delivery_date >= so_item.delivery_date,
"to_delivery_date": self.to_delivery_date <= so_item.delivery_date,
}
for field, value in date_field_mapper.items():
if self.get(field):
so_filter += f" and {value[1]} {value[0]} %({field})s"
open_so_query = open_so_query.where(value)
for field in ["customer", "project", "sales_order_status"]:
for field in ("customer", "project", "sales_order_status"):
if self.get(field):
so_field = "status" if field == "sales_order_status" else field
so_filter += f" and so.{so_field} = %({field})s"
open_so_query = open_so_query.where(so[so_field] == self.get(field))
if self.item_code and frappe.db.exists("Item", self.item_code):
bom_item = self.get_bom_item() or bom_item
item_filter += " and so_item.item_code = %(item_code)s"
open_so_query = open_so_query.where(so_item.item_code == self.item_code)
open_so_subquery1 = open_so_subquery1.where(
self.get_bom_item_condition() or bom.item == so_item.item_code
)
open_so = frappe.db.sql(
f"""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
and so.company = %(company)s
and so_item.qty > so_item.work_order_qty {so_filter} {item_filter}
and (exists (select name from `tabBOM` bom where {bom_item}
and bom.is_active = 1)
or exists (select name from `tabPacked Item` pi
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
""",
self.as_dict(),
as_dict=1,
open_so_query = open_so_query.where(
(ExistsCriterion(open_so_subquery1) | ExistsCriterion(open_so_subquery2))
)
open_so = open_so_query.run(as_dict=True)
return open_so
@@ -965,37 +1073,34 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
if isinstance(row, str):
row = frappe._dict(json.loads(row))
company = frappe.db.escape(company)
conditions, warehouse = "", ""
bin = frappe.qb.DocType("Bin")
wh = frappe.qb.DocType("Warehouse")
subquery = frappe.qb.from_(wh).select(wh.name).where(wh.company == company)
conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(
company
)
if not all_warehouse:
warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse")
if warehouse:
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
conditions = """ and warehouse in (select name from `tabWarehouse`
where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse and company = {2})
""".format(
lft, rgt, company
)
subquery = subquery.where((wh.lft >= lft) & (wh.rgt <= rgt) & (wh.name == bin.warehouse))
return frappe.db.sql(
""" select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty,
ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse,
ifnull(sum(planned_qty),0) as planned_qty
from `tabBin` where item_code = %(item_code)s {conditions}
group by item_code, warehouse
""".format(
conditions=conditions
),
{"item_code": row["item_code"]},
as_dict=1,
query = (
frappe.qb.from_(bin)
.select(
bin.warehouse,
IfNull(Sum(bin.projected_qty), 0).as_("projected_qty"),
IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"),
IfNull(Sum(bin.ordered_qty), 0).as_("ordered_qty"),
IfNull(Sum(bin.reserved_qty_for_production), 0).as_("reserved_qty_for_production"),
IfNull(Sum(bin.planned_qty), 0).as_("planned_qty"),
)
.where((bin.item_code == row["item_code"]) & (bin.warehouse.isin(subquery)))
.groupby(bin.item_code, bin.warehouse)
)
return query.run(as_dict=True)
@frappe.whitelist()
def get_so_details(sales_order):

View File

@@ -12,6 +12,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
)
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -538,15 +539,21 @@ class TestProductionPlan(FrappeTestCase):
"""
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
make_stock_entry(item_code="_Test Item", target="Work In Progress - _TC", qty=2, basic_rate=100)
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
)
make_stock_entry(
item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100
item_code="_Test Item Home Desktop 100", target="Work In Progress - _TC", qty=4, basic_rate=100
)
item = "Test Production Item 1"
so = make_sales_order(item_code=item, qty=1)
item = "_Test FG Item"
make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=1)
so = make_sales_order(item_code=item, qty=2)
dn = make_delivery_note(so.name)
dn.items[0].qty = 1
dn.save()
dn.submit()
pln = create_production_plan(
company=so.company, get_items_from="Sales Order", sales_order=so, skip_getting_mr_items=True

View File

@@ -95,7 +95,7 @@ class TestWorkOrder(FrappeTestCase):
def test_planned_operating_cost(self):
wo_order = make_wo_order_test_record(
item="_Test FG Item 2", planned_start_date=now(), qty=1, do_not_save=True
item="_Test FG Item 3", planned_start_date=now(), qty=1, do_not_save=True
)
wo_order.set_work_order_operations()
cost = wo_order.planned_operating_cost
@@ -1001,6 +1001,49 @@ class TestWorkOrder(FrappeTestCase):
close_work_order(wo_order, "Closed")
self.assertEqual(wo_order.get("status"), "Closed")
def test_fix_time_operations(self):
bom = frappe.get_doc(
{
"doctype": "BOM",
"item": "_Test FG Item 2",
"is_active": 1,
"is_default": 1,
"quantity": 1.0,
"with_operations": 1,
"operations": [
{
"operation": "_Test Operation 1",
"description": "_Test",
"workstation": "_Test Workstation 1",
"time_in_mins": 60,
"operating_cost": 140,
"fixed_time": 1,
}
],
"items": [
{
"amount": 5000.0,
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
"qty": 1.0,
"rate": 5000.0,
},
],
}
)
bom.save()
bom.submit()
wo1 = make_wo_order_test_record(
item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1
)
wo2 = make_wo_order_test_record(
item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1
)
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
def test_partial_manufacture_entries(self):
cancel_stock_entry = []
@@ -1015,7 +1058,6 @@ class TestWorkOrder(FrappeTestCase):
ste1 = test_stock_entry.make_stock_entry(
item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0
)
ste2 = test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",

View File

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

View File

@@ -653,20 +653,31 @@ class WorkOrder(Document):
"""Fetch operations from BOM and set in 'Work Order'"""
def _get_operations(bom_no, qty=1):
return frappe.db.sql(
f"""select
operation, description, workstation, idx,
base_hour_rate as hour_rate, time_in_mins * {qty} as time_in_mins,
"Pending" as status, parent as bom, batch_size, sequence_id
from
`tabBOM Operation`
where
parent = %s order by idx
""",
bom_no,
as_dict=1,
data = frappe.get_all(
"BOM Operation",
filters={"parent": bom_no},
fields=[
"operation",
"description",
"workstation",
"idx",
"base_hour_rate as hour_rate",
"time_in_mins",
"parent as bom",
"batch_size",
"sequence_id",
"fixed_time",
],
order_by="idx",
)
for d in data:
if not d.fixed_time:
d.time_in_mins = flt(d.time_in_mins) * flt(qty)
d.status = "Pending"
return data
self.set("operations", [])
if not self.bom_no or not frappe.get_cached_value("BOM", self.bom_no, "with_operations"):
return
@@ -692,7 +703,8 @@ class WorkOrder(Document):
def calculate_time(self):
for d in self.get("operations"):
d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size))
if not d.fixed_time:
d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size))
self.calculate_operating_cost()

View File

@@ -11,17 +11,24 @@ frappe.query_reports["BOM Stock Calculated"] = {
"options": "BOM",
"reqd": 1
},
{
"fieldname": "qty_to_make",
"label": __("Quantity to Make"),
"fieldtype": "Int",
"default": "1"
},
{
{
"fieldname": "warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
},
{
"fieldname": "qty_to_make",
"label": __("Quantity to Make"),
"fieldtype": "Float",
"default": "1.0",
"reqd": 1
},
{
"fieldname": "show_exploded_view",
"label": __("Show exploded view"),
"fieldtype": "Check"
"fieldtype": "Check",
"default": false,
}
]
}

View File

@@ -4,29 +4,31 @@
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils.data import comma_and
from pypika.terms import ExistsCriterion
def execute(filters=None):
# if not filters: filters = {}
columns = get_columns()
summ_data = []
data = []
data = get_bom_stock(filters)
bom_data = get_bom_data(filters)
qty_to_make = filters.get("qty_to_make")
manufacture_details = get_manufacturer_records()
for row in data:
reqd_qty = qty_to_make * row.actual_qty
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
return columns, summ_data
for row in bom_data:
required_qty = qty_to_make * row.qty_per_unit
last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details))
return columns, data
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
to_build = row.to_build if row.to_build > 0 else 0
diff_qty = to_build - reqd_qty
def get_report_data(last_purchase_rate, required_qty, row, manufacture_details):
qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0
difference_qty = row.actual_qty - required_qty
return [
row.item_code,
row.description,
@@ -34,85 +36,126 @@ def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
comma_and(
manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
),
qty_per_unit,
row.actual_qty,
str(to_build),
reqd_qty,
diff_qty,
last_pur_price,
required_qty,
difference_qty,
last_purchase_rate,
]
def get_columns():
"""return columns"""
columns = [
_("Item") + ":Link/Item:100",
_("Description") + "::150",
_("Manufacturer") + "::250",
_("Manufacturer Part Number") + "::250",
_("Qty") + ":Float:50",
_("Stock Qty") + ":Float:100",
_("Reqd Qty") + ":Float:100",
_("Diff Qty") + ":Float:100",
_("Last Purchase Price") + ":Float:100",
return [
{
"fieldname": "item",
"label": _("Item"),
"fieldtype": "Link",
"options": "Item",
"width": 120,
},
{
"fieldname": "description",
"label": _("Description"),
"fieldtype": "Data",
"width": 150,
},
{
"fieldname": "manufacturer",
"label": _("Manufacturer"),
"fieldtype": "Data",
"width": 120,
},
{
"fieldname": "manufacturer_part_number",
"label": _("Manufacturer Part Number"),
"fieldtype": "Data",
"width": 150,
},
{
"fieldname": "qty_per_unit",
"label": _("Qty Per Unit"),
"fieldtype": "Float",
"width": 110,
},
{
"fieldname": "available_qty",
"label": _("Available Qty"),
"fieldtype": "Float",
"width": 120,
},
{
"fieldname": "required_qty",
"label": _("Required Qty"),
"fieldtype": "Float",
"width": 120,
},
{
"fieldname": "difference_qty",
"label": _("Difference Qty"),
"fieldtype": "Float",
"width": 130,
},
{
"fieldname": "last_purchase_rate",
"label": _("Last Purchase Rate"),
"fieldtype": "Float",
"width": 160,
},
]
return columns
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
table = "`tabBOM Item`"
qty_field = "qty"
def get_bom_data(filters):
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
qty_field = "stock_qty"
bom_item_table = "BOM Explosion Item"
else:
bom_item_table = "BOM Item"
bom_item = frappe.qb.DocType(bom_item_table)
bin = frappe.qb.DocType("Bin")
query = (
frappe.qb.from_(bom_item)
.left_join(bin)
.on(bom_item.item_code == bin.item_code)
.select(
bom_item.item_code,
bom_item.description,
bom_item.qty_consumed_per_unit.as_("qty_per_unit"),
IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
)
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
if warehouse_details:
conditions += (
" and exists (select name from `tabWarehouse` wh \
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
% (warehouse_details.lft, warehouse_details.rgt)
wh = frappe.qb.DocType("Warehouse")
query = query.where(
ExistsCriterion(
frappe.qb.from_(wh)
.select(wh.name)
.where(
(wh.lft >= warehouse_details.lft)
& (wh.rgt <= warehouse_details.rgt)
& (bin.warehouse == wh.name)
)
)
)
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
query = query.where(bin.warehouse == filters.get("warehouse"))
else:
conditions += ""
return frappe.db.sql(
"""
SELECT
bom_item.item_code,
bom_item.description,
bom_item.{qty_field},
ifnull(sum(ledger.actual_qty), 0) as actual_qty,
ifnull(sum(FLOOR(ledger.actual_qty / bom_item.{qty_field})), 0) as to_build
FROM
{table} AS bom_item
LEFT JOIN `tabBin` AS ledger
ON bom_item.item_code = ledger.item_code
{conditions}
WHERE
bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
qty_field=qty_field, table=table, conditions=conditions, bom=bom
),
as_dict=1,
)
return query.run(as_dict=True)
def get_manufacturer_records():
details = frappe.get_all(
"Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
)
manufacture_details = frappe._dict()
for detail in details:
dic = manufacture_details.setdefault(detail.get("item_code"), {})

View File

@@ -0,0 +1,115 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.report.bom_stock_calculated.bom_stock_calculated import (
execute as bom_stock_calculated_report,
)
from erpnext.stock.doctype.item.test_item import make_item
class TestBOMStockCalculated(FrappeTestCase):
def setUp(self):
self.fg_item, self.rm_items = create_items()
self.boms = create_boms(self.fg_item, self.rm_items)
def test_bom_stock_calculated(self):
qty_to_make = 10
# Case 1: When Item(s) Qty and Stock Qty are equal.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[0].name,
}
)[1]
expected_data = get_expected_data(self.boms[0], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
# Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[1].name,
}
)[1]
expected_data = get_expected_data(self.boms[1], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
# Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[2].name,
}
)[1]
expected_data = get_expected_data(self.boms[2], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
def create_items():
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item1 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 100,
"opening_stock": 100,
"last_purchase_rate": 100,
}
).name
rm_item2 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 200,
"opening_stock": 200,
"last_purchase_rate": 200,
}
).name
return fg_item, [rm_item1, rm_item2]
def create_boms(fg_item, rm_items):
def update_bom_items(bom, uom, conversion_factor):
for item in bom.items:
item.uom = uom
item.conversion_factor = conversion_factor
return bom
bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10)
bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
bom2 = update_bom_items(bom2, "Box", 10)
bom2.save()
bom2.submit()
bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
bom3 = update_bom_items(bom3, "Box", 10)
bom3.save()
bom3.submit()
return [bom1, bom2, bom3]
def get_expected_data(bom, qty_to_make):
expected_data = []
for idx in range(len(bom.items)):
expected_data.append(
[
bom.items[idx].item_code,
bom.items[idx].item_code,
"",
"",
float(bom.items[idx].stock_qty / bom.quantity),
float(100 * (idx + 1)),
float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)),
float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))),
float(100 * (idx + 1)),
]
)
return expected_data

View File

@@ -4,6 +4,8 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Floor, Sum
from pypika.terms import ExistsCriterion
def execute(filters=None):
@@ -11,7 +13,6 @@ def execute(filters=None):
filters = {}
columns = get_columns()
data = get_bom_stock(filters)
return columns, data
@@ -33,59 +34,57 @@ def get_columns():
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
table = "`tabBOM Item`"
qty_field = "stock_qty"
qty_to_produce = filters.get("qty_to_produce", 1)
if int(qty_to_produce) <= 0:
qty_to_produce = filters.get("qty_to_produce") or 1
if int(qty_to_produce) < 0:
frappe.throw(_("Quantity to Produce can not be less than Zero"))
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
bom_item_table = "BOM Explosion Item"
else:
bom_item_table = "BOM Item"
bin = frappe.qb.DocType("Bin")
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType(bom_item_table)
query = (
frappe.qb.from_(bom)
.inner_join(bom_item)
.on(bom.name == bom_item.parent)
.left_join(bin)
.on(bom_item.item_code == bin.item_code)
.select(
bom_item.item_code,
bom_item.description,
bom_item.stock_qty,
bom_item.stock_uom,
bom_item.stock_qty * qty_to_produce / bom.quantity,
Sum(bin.actual_qty).as_("actual_qty"),
Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
)
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
if warehouse_details:
conditions += (
" and exists (select name from `tabWarehouse` wh \
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
% (warehouse_details.lft, warehouse_details.rgt)
wh = frappe.qb.DocType("Warehouse")
query = query.where(
ExistsCriterion(
frappe.qb.from_(wh)
.select(wh.name)
.where(
(wh.lft >= warehouse_details.lft)
& (wh.rgt <= warehouse_details.rgt)
& (bin.warehouse == wh.name)
)
)
)
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
query = query.where(bin.warehouse == filters.get("warehouse"))
else:
conditions += ""
return frappe.db.sql(
"""
SELECT
bom_item.item_code,
bom_item.description ,
bom_item.{qty_field},
bom_item.stock_uom,
bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
sum(ledger.actual_qty) as actual_qty,
sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))
FROM
`tabBOM` AS bom INNER JOIN {table} AS bom_item
ON bom.name = bom_item.parent
LEFT JOIN `tabBin` AS ledger
ON bom_item.item_code = ledger.item_code
{conditions}
WHERE
bom_item.parent = {bom} and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
qty_field=qty_field,
table=table,
conditions=conditions,
bom=frappe.db.escape(bom),
qty_to_produce=qty_to_produce or 1,
)
)
return query.run()

View File

@@ -64,22 +64,21 @@ def get_columns(filters):
def get_data(filters):
cond = "1=1"
wo = frappe.qb.DocType("Work Order")
query = (
frappe.qb.from_(wo)
.select(wo.name.as_("work_order"), wo.qty, wo.produced_qty, wo.production_item, wo.bom_no)
.where((wo.produced_qty > wo.qty) & (wo.docstatus == 1))
)
if filters.get("bom_no") and not filters.get("work_order"):
cond += " and bom_no = '%s'" % filters.get("bom_no")
query = query.where(wo.bom_no == filters.get("bom_no"))
if filters.get("work_order"):
cond += " and name = '%s'" % filters.get("work_order")
query = query.where(wo.name == filters.get("work_order"))
results = []
for d in frappe.db.sql(
""" select name as work_order, qty, produced_qty, production_item, bom_no
from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format(
cond
),
as_dict=1,
):
for d in query.run(as_dict=True):
results.append(d)
for data in frappe.get_all(
@@ -95,16 +94,17 @@ def get_data(filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_work_orders(doctype, txt, searchfield, start, page_len, filters):
cond = "1=1"
if filters.get("bom_no"):
cond += " and bom_no = '%s'" % filters.get("bom_no")
return frappe.db.sql(
"""select name from `tabWork Order`
where name like %(name)s and {0} and produced_qty > qty and docstatus = 1
order by name limit {1}, {2}""".format(
cond, start, page_len
),
{"name": "%%%s%%" % txt},
as_list=1,
wo = frappe.qb.DocType("Work Order")
query = (
frappe.qb.from_(wo)
.select(wo.name)
.where((wo.name.like(f"{txt}%")) & (wo.produced_qty > wo.qty) & (wo.docstatus == 1))
.orderby(wo.name)
.limit(page_len)
.offset(start)
)
if filters.get("bom_no"):
query = query.where(wo.bom_no == filters.get("bom_no"))
return query.run(as_list=True)

View File

@@ -96,38 +96,39 @@ class ForecastingReport(ExponentialSmoothingForecast):
value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty))
def get_data_for_forecast(self):
cond = ""
if self.filters.item_code:
cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code))
warehouses = []
if self.filters.warehouse:
warehouses = get_child_warehouses(self.filters.warehouse)
cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses)))
input_data = [self.filters.from_date, self.filters.company]
if warehouses:
input_data.extend(warehouses)
parent = frappe.qb.DocType(self.doctype)
child = frappe.qb.DocType(self.child_doctype)
date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date"
return frappe.db.sql(
"""
SELECT
so.{date_field} as posting_date, soi.item_code, soi.warehouse,
soi.item_name, soi.stock_qty as qty, soi.base_amount as amount
FROM
`tab{doc}` so, `tab{child_doc}` soi
WHERE
so.docstatus = 1 AND so.name = soi.parent AND
so.{date_field} < %s AND so.company = %s {cond}
""".format(
doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond
),
tuple(input_data),
as_dict=1,
query = (
frappe.qb.from_(parent)
.from_(child)
.select(
parent[date_field].as_("posting_date"),
child.item_code,
child.warehouse,
child.item_name,
child.stock_qty.as_("qty"),
child.base_amount.as_("amount"),
)
.where(
(parent.docstatus == 1)
& (parent.name == child.parent)
& (parent[date_field] < self.filters.from_date)
& (parent.company == self.filters.company)
)
)
if self.filters.item_code:
query = query.where(child.item_code == self.filters.item_code)
if self.filters.warehouse:
warehouses = get_child_warehouses(self.filters.warehouse) or []
query = query.where(child.warehouse.isin(warehouses))
return query.run(as_dict=True)
def prepare_final_data(self):
self.data = []

View File

@@ -5,6 +5,7 @@ from typing import Dict, List, Tuple
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
Filters = frappe._dict
Row = frappe._dict
@@ -14,15 +15,50 @@ QueryArgs = Dict[str, str]
def execute(filters: Filters) -> Tuple[Columns, Data]:
filters = frappe._dict(filters or {})
columns = get_columns()
data = get_data(filters)
return columns, data
def get_data(filters: Filters) -> Data:
query_args = get_query_args(filters)
data = run_query(query_args)
wo = frappe.qb.DocType("Work Order")
se = frappe.qb.DocType("Stock Entry")
query = (
frappe.qb.from_(wo)
.inner_join(se)
.on(wo.name == se.work_order)
.select(
wo.name,
wo.status,
wo.production_item,
wo.qty,
wo.produced_qty,
wo.process_loss_qty,
(wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"),
Sum(se.total_incoming_value).as_("total_fg_value"),
Sum(se.total_outgoing_value).as_("total_rm_value"),
)
.where(
(wo.process_loss_qty > 0)
& (wo.company == filters.company)
& (se.docstatus == 1)
& (se.posting_date.between(filters.from_date, filters.to_date))
)
.groupby(se.work_order)
)
if "item" in filters:
query.where(wo.production_item == filters.item)
if "work_order" in filters:
query.where(wo.name == filters.work_order)
data = query.run(as_dict=True)
update_data_with_total_pl_value(data)
return data
@@ -67,54 +103,7 @@ def get_columns() -> Columns:
]
def get_query_args(filters: Filters) -> QueryArgs:
query_args = {}
query_args.update(filters)
query_args.update(get_filter_conditions(filters))
return query_args
def run_query(query_args: QueryArgs) -> Data:
return frappe.db.sql(
"""
SELECT
wo.name, wo.status, wo.production_item, wo.qty,
wo.produced_qty, wo.process_loss_qty,
(wo.produced_qty - wo.process_loss_qty) as actual_produced_qty,
sum(se.total_incoming_value) as total_fg_value,
sum(se.total_outgoing_value) as total_rm_value
FROM
`tabWork Order` wo INNER JOIN `tabStock Entry` se
ON wo.name=se.work_order
WHERE
process_loss_qty > 0
AND wo.company = %(company)s
AND se.docstatus = 1
AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s
{item_filter}
{work_order_filter}
GROUP BY
se.work_order
""".format(
**query_args
),
query_args,
as_dict=1,
)
def update_data_with_total_pl_value(data: Data) -> None:
for row in data:
value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg
def get_filter_conditions(filters: Filters) -> QueryArgs:
filter_conditions = dict(item_filter="", work_order_filter="")
if "item" in filters:
production_item = filters.get("item")
filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"})
if "work_order" in filters:
work_order_name = filters.get("work_order")
filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"})
return filter_conditions

View File

@@ -4,42 +4,10 @@
import frappe
from frappe import _
from pypika import Order
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
# and bom_no is not null and bom_no !=''
mapper = {
"Sales Order": {
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
`tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
"filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
and `tabSales Order`.per_delivered < 100.0""",
},
"Material Request": {
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
`tabMaterial Request Item`.schedule_date """,
"filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
and `tabMaterial Request`.material_request_type = 'Manufacture' """,
},
"Work Order": {
"fields": """ production_item, item_name as production_item_name, planned_start_date,
stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
"filters": "docstatus = 1 and status not in ('Completed', 'Stopped')",
},
}
order_mapper = {
"Sales Order": {
"Delivery Date": "`tabSales Order Item`.delivery_date asc",
"Total Amount": "`tabSales Order`.base_grand_total desc",
},
"Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"},
"Work Order": {"Planned Start Date": "planned_start_date asc"},
}
def execute(filters=None):
return ProductionPlanReport(filters).execute_report()
@@ -63,40 +31,78 @@ class ProductionPlanReport(object):
return self.columns, self.data
def get_open_orders(self):
doctype = (
"`tabWork Order`"
if self.filters.based_on == "Work Order"
else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on)
)
doctype, order_by = self.filters.based_on, self.filters.order_by
filters = mapper.get(self.filters.based_on)["filters"]
filters = self.prepare_other_conditions(filters, self.filters.based_on)
order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
parent = frappe.qb.DocType(doctype)
query = None
self.orders = frappe.db.sql(
""" SELECT {fields} from {doctype}
WHERE {filters} {order_by}""".format(
doctype=doctype,
filters=filters,
order_by=order_by,
fields=mapper.get(self.filters.based_on)["fields"],
),
tuple(self.filters.docnames),
as_dict=1,
)
if doctype == "Work Order":
query = (
frappe.qb.from_(parent)
.select(
parent.production_item,
parent.item_name.as_("production_item_name"),
parent.planned_start_date,
parent.stock_uom,
parent.qty.as_("qty_to_manufacture"),
parent.name,
parent.bom_no,
parent.fg_warehouse.as_("warehouse"),
)
.where(parent.status.notin(["Completed", "Stopped"]))
)
def prepare_other_conditions(self, filters, doctype):
if self.filters.docnames:
field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames)))
if order_by == "Planned Start Date":
query = query.orderby(parent.planned_start_date, order=Order.asc)
if doctype != "Work Order":
filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
if self.filters.docnames:
query = query.where(parent.name.isin(self.filters.docnames))
else:
child = frappe.qb.DocType(f"{doctype} Item")
query = (
frappe.qb.from_(parent)
.from_(child)
.select(
child.bom_no,
child.stock_uom,
child.warehouse,
child.parent.as_("name"),
child.item_code.as_("production_item"),
child.stock_qty.as_("qty_to_manufacture"),
child.item_name.as_("production_item_name"),
)
.where(parent.name == child.parent)
)
if self.filters.docnames:
query = query.where(child.parent.isin(self.filters.docnames))
if doctype == "Sales Order":
query = query.select(
child.delivery_date,
parent.base_grand_total,
).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0))
if order_by == "Delivery Date":
query = query.orderby(child.delivery_date, order=Order.asc)
elif order_by == "Total Amount":
query = query.orderby(parent.base_grand_total, order=Order.desc)
elif doctype == "Material Request":
query = query.select(child.schedule_date,).where(
(parent.per_ordered < 100) & (parent.material_request_type == "Manufacture")
)
if order_by == "Required Date":
query = query.orderby(child.schedule_date, order=Order.asc)
query = query.where(parent.docstatus == 1)
if self.filters.company:
filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company))
query = query.where(parent.company == self.filters.company)
return filters
self.orders = query.run(as_dict=True)
def get_raw_materials(self):
if not self.orders:
@@ -134,29 +140,29 @@ class ProductionPlanReport(object):
bom_nos.append(bom_no)
bom_doctype = (
bom_item_doctype = (
"BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item"
)
qty_field = (
"qty_consumed_per_unit"
if self.filters.include_subassembly_raw_materials
else "(bom_item.qty / bom.quantity)"
)
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType(bom_item_doctype)
raw_materials = frappe.db.sql(
""" SELECT bom_item.parent, bom_item.item_code,
bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM
`tabBOM` as bom, `tab{1}` as bom_item
WHERE
bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
""".format(
qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos))
),
tuple(bom_nos),
as_dict=1,
)
if self.filters.include_subassembly_raw_materials:
qty_field = bom_item.qty_consumed_per_unit
else:
qty_field = bom_item.qty / bom.quantity
raw_materials = (
frappe.qb.from_(bom)
.from_(bom_item)
.select(
bom_item.parent,
bom_item.item_code,
bom_item.item_name.as_("raw_material_name"),
qty_field.as_("required_qty_per_unit"),
)
.where((bom_item.parent.isin(bom_nos)) & (bom_item.parent == bom.name) & (bom.docstatus == 1))
).run(as_dict=True)
if not raw_materials:
return

View File

@@ -3,6 +3,7 @@
import frappe
from frappe.query_builder.functions import IfNull
from frappe.utils import cint
@@ -16,70 +17,70 @@ def execute(filters=None):
def get_item_list(wo_list, filters):
out = []
# Add a row for each item/qty
for wo_details in wo_list:
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
if wo_list:
bin = frappe.qb.DocType("Bin")
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType("BOM Item")
for wo_item_details in frappe.db.get_values(
"Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
):
# Add a row for each item/qty
for wo_details in wo_list:
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
item_list = frappe.db.sql(
"""SELECT
bom_item.item_code as item_code,
ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty
FROM
`tabBOM` as bom, `tabBOM Item` AS bom_item
LEFT JOIN `tabBin` AS ledger
ON bom_item.item_code = ledger.item_code
AND ledger.warehouse = ifnull(%(warehouse)s,%(filterhouse)s)
WHERE
bom.name = bom_item.parent
and bom_item.item_code = %(item_code)s
and bom.name = %(bom)s
GROUP BY
bom_item.item_code""",
{
"bom": wo_details.bom_no,
"warehouse": wo_item_details.source_warehouse,
"filterhouse": filters.warehouse,
"item_code": wo_item_details.item_code,
},
as_dict=1,
)
for wo_item_details in frappe.db.get_values(
"Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
):
item_list = (
frappe.qb.from_(bom)
.from_(bom_item)
.left_join(bin)
.on(
(bom_item.item_code == bin.item_code)
& (bin.warehouse == IfNull(wo_item_details.source_warehouse, filters.warehouse))
)
.select(
bom_item.item_code.as_("item_code"),
IfNull(bin.actual_qty * bom.quantity / bom_item.stock_qty, 0).as_("build_qty"),
)
.where(
(bom.name == bom_item.parent)
& (bom_item.item_code == wo_item_details.item_code)
& (bom.name == wo_details.bom_no)
)
.groupby(bom_item.item_code)
).run(as_dict=1)
stock_qty = 0
count = 0
buildable_qty = wo_details.qty
for item in item_list:
count = count + 1
if item.build_qty >= (wo_details.qty - wo_details.produced_qty):
stock_qty = stock_qty + 1
elif buildable_qty >= item.build_qty:
buildable_qty = item.build_qty
stock_qty = 0
count = 0
buildable_qty = wo_details.qty
for item in item_list:
count = count + 1
if item.build_qty >= (wo_details.qty - wo_details.produced_qty):
stock_qty = stock_qty + 1
elif buildable_qty >= item.build_qty:
buildable_qty = item.build_qty
if count == stock_qty:
build = "Y"
else:
build = "N"
if count == stock_qty:
build = "Y"
else:
build = "N"
row = frappe._dict(
{
"work_order": wo_details.name,
"status": wo_details.status,
"req_items": cint(count),
"instock": stock_qty,
"description": desc,
"source_warehouse": wo_item_details.source_warehouse,
"item_code": wo_item_details.item_code,
"bom_no": wo_details.bom_no,
"qty": wo_details.qty,
"buildable_qty": buildable_qty,
"ready_to_build": build,
}
)
row = frappe._dict(
{
"work_order": wo_details.name,
"status": wo_details.status,
"req_items": cint(count),
"instock": stock_qty,
"description": desc,
"source_warehouse": wo_item_details.source_warehouse,
"item_code": wo_item_details.item_code,
"bom_no": wo_details.bom_no,
"qty": wo_details.qty,
"buildable_qty": buildable_qty,
"ready_to_build": build,
}
)
out.append(row)
out.append(row)
return out

View File

@@ -373,3 +373,4 @@ erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0})

View File

@@ -16,18 +16,18 @@ def execute():
delete_auto_email_reports(report)
check_and_delete_linked_reports(report)
frappe.delete_doc("Report", report)
frappe.delete_doc("Report", report, force=True)
def delete_auto_email_reports(report):
"""Check for one or multiple Auto Email Reports and delete"""
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
frappe.delete_doc("Auto Email Report", auto_email_report[0], force=True)
def delete_links_from_desktop_icons(report):
"""Check for one or multiple Desktop Icons and delete"""
desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
for desktop_icon in desktop_icons:
frappe.delete_doc("Desktop Icon", desktop_icon[0])
frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True)

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