Compare commits

...

189 Commits

Author SHA1 Message Date
Frappe PR Bot
53024be347 chore(release): Bumped to Version 13.28.0
# [13.28.0](https://github.com/frappe/erpnext/compare/v13.27.1...v13.28.0) (2022-05-03)

### Bug Fixes

* Consistent accounting dimensions across Sales and Purchase docs ([5df5058](5df50588cf))
* convert default_item_manufacturer to link field ([#30835](https://github.com/frappe/erpnext/issues/30835)) ([#30866](https://github.com/frappe/erpnext/issues/30866)) ([37fad7e](37fad7e04c))
* Cost center filter on payment reconciliation ([22e7f03](22e7f03a03))
* filters not working in Shift Assignment Calendar view ([#30822](https://github.com/frappe/erpnext/issues/30822)) ([3cdbb65](3cdbb65b5a))
* Ignore custom field validation while setup ([ee54bf7](ee54bf7fe2))
* **india:** e-invoice generation for registered composition gst category type ([#30814](https://github.com/frappe/erpnext/issues/30814)) ([#30877](https://github.com/frappe/erpnext/issues/30877)) ([246869d](246869dd28))
* Multi currency opening invoices ([2e62d51](2e62d518e8))
* payment days calculation for employees joining/leaving mid-month ([#30863](https://github.com/frappe/erpnext/issues/30863)) ([#30883](https://github.com/frappe/erpnext/issues/30883)) ([a1b0813](a1b0813966))
* Period Closing Voucher is considering GL entries with is_cancelled=1 ([#30865](https://github.com/frappe/erpnext/issues/30865)) ([5a5b49b](5a5b49b61a))
* **pos:** number pad translations ([b01f855](b01f8555e5))
* **pos:** search field doesn't reset on checkout ([edbf551](edbf5513da))
* Supply type for overseas invoices with payment of tax ([fdcc591](fdcc591a5e))
* supply type for sez invoices with payment of tax ([cf08710](cf087103cb))
* Use `account_type == 'Stock'` to filter stock accounts ([93482f3](93482f3302))
* **UX:** misleading stock entry lables ([#30870](https://github.com/frappe/erpnext/issues/30870)) ([#30871](https://github.com/frappe/erpnext/issues/30871)) ([57b03f0](57b03f0bf2))
* **UX:** record reason for skipping attendance or marking absent for auto attendance ([#30846](https://github.com/frappe/erpnext/issues/30846)) ([f7bf4a3](f7bf4a3e62))
* Vat Audit report fixes ([d5319a4](d5319a4826))

### Features

* Copy task color from project template (backport [#30857](https://github.com/frappe/erpnext/issues/30857)) ([#30859](https://github.com/frappe/erpnext/issues/30859)) ([7ee18e8](7ee18e86a2))
* support product bundles in picklist (backport [#30762](https://github.com/frappe/erpnext/issues/30762)) ([#30826](https://github.com/frappe/erpnext/issues/30826)) ([645ee2d](645ee2d822))
2022-05-03 08:51:32 +00:00
Deepesh Garg
a1f31013e2 Merge pull request #30886 from frappe/version-13-pre-release
chore: weekly release for version-13
2022-05-03 14:19:46 +05:30
Deepesh Garg
64d96f436b Merge pull request #30885 from frappe/version-13-hotfix
chore: pre release for version-13
2022-05-03 13:41:33 +05:30
Deepesh Garg
fda250606a Merge branch 'version-13-pre-release' into version-13-hotfix 2022-05-03 13:21:10 +05:30
Deepesh Garg
2126dee8ca Merge pull request #30874 from frappe/mergify/bp/version-13-hotfix/pr-30848
fix: Cost center filter in payment reconciliation (backport #30848)
2022-05-03 13:18:31 +05:30
Deepesh Garg
937d358fbf Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30848 2022-05-03 12:46:10 +05:30
Deepesh Garg
366cd6171c chore: resolve conflicts 2022-05-03 12:21:06 +05:30
mergify[bot]
a1b0813966 fix: payment days calculation for employees joining/leaving mid-month (#30863) (#30883)
(cherry picked from commit 924cf7763e)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-05-03 11:46:46 +05:30
Saqib Ansari
4db588e72e Merge pull request #30876 from frappe/mergify/bp/version-13-hotfix/pr-30875
fix(India): Supply type for overseas invoices with payment of tax (backport #30875)
2022-05-02 22:17:56 +05:30
mergify[bot]
246869dd28 fix(india): e-invoice generation for registered composition gst category type (#30814) (#30877) 2022-05-02 22:16:43 +05:30
Saqib Ansari
cf087103cb fix: supply type for sez invoices with payment of tax
(cherry picked from commit c8aa77285e)
2022-05-02 16:20:44 +00:00
Deepesh Garg
fdcc591a5e fix: Supply type for overseas invoices with payment of tax
(cherry picked from commit d7cb269e0c)
2022-05-02 16:20:44 +00:00
Deepesh Garg
0df96e1084 test: Add test for payment reconciliation
(cherry picked from commit b440dabe12)
2022-05-02 12:29:25 +00:00
Deepesh Garg
22e7f03a03 fix: Cost center filter on payment reconciliation
(cherry picked from commit ab94b73e93)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
2022-05-02 12:29:24 +00:00
Deepesh Garg
6aab9ca427 Merge pull request #30852 from deepeshgarg007/vat_ksa
fix: Vat Audit report fixes
2022-05-02 16:14:03 +05:30
mergify[bot]
57b03f0bf2 fix(UX): misleading stock entry lables (#30870) (#30871)
* fix(UX): misleading stock entry lables

* chore: field labels

[skip ci]

Co-authored-by: Marica <maricadsouza221197@gmail.com>

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 59a5090843)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-05-02 15:17:13 +05:30
Sherin KR
5a5b49b61a fix: Period Closing Voucher is considering GL entries with is_cancelled=1 (#30865)
* fix: Period Closing Voucher - Period Closing Voucher is considering GL entry with is_cancelled=1 as well

* fix: condition

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-05-02 15:03:30 +05:30
mergify[bot]
37fad7e04c fix: convert default_item_manufacturer to link field (#30835) (#30866)
(cherry picked from commit dcda55641b)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-05-02 14:58:05 +05:30
Deepesh Garg
7c0069c0d3 chore: Education domain deprecation warning (#30861)
* chore: Education domain deprecation warning

* chore: Education domain deprecation warning

* chore: Rename file and patch
2022-05-02 13:29:25 +05:30
Deepesh Garg
0b9a59c605 Merge pull request #30856 from frappe/mergify/bp/version-13-hotfix/pr-30806
fix: Consistent accounting dimensions across Sales and Purchase docs (backport #30806)
2022-05-02 11:44:52 +05:30
Deepesh Garg
b9c326e3f6 chore: resolve conflicts 2022-05-02 11:16:48 +05:30
Deepesh Garg
d9756d54ad chore: resolve conflicts 2022-05-02 11:15:58 +05:30
mergify[bot]
7ee18e86a2 feat: Copy task color from project template (backport #30857) (#30859)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: sersaber <93864988+sersaber@users.noreply.github.com>
2022-05-02 10:59:58 +05:30
Deepesh Garg
5df50588cf fix: Consistent accounting dimensions across Sales and Purchase docs
(cherry picked from commit 82a0635c66)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/selling/doctype/sales_order/sales_order.json
2022-05-01 16:33:16 +00:00
Deepesh Garg
87383680f0 Merge pull request #30853 from frappe/mergify/bp/version-13-hotfix/pr-30815
fix: Ignore custom field validation while setup (backport #30815)
2022-05-01 15:22:57 +05:30
Deepesh Garg
ee54bf7fe2 fix: Ignore custom field validation while setup
(cherry picked from commit 67bb29026f)
2022-04-30 16:33:44 +00:00
Deepesh Garg
d5319a4826 fix: Vat Audit report fixes 2022-04-30 22:01:05 +05:30
Deepesh Garg
a98263cd31 Merge pull request #30845 from frappe/mergify/bp/version-13-hotfix/pr-30843
fix: Multi currency opening invoices (backport #30843)
2022-04-29 23:04:03 +05:30
Deepesh Garg
a3de0320aa Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30843 2022-04-29 22:16:52 +05:30
Rucha Mahabal
f7bf4a3e62 fix(UX): record reason for skipping attendance or marking absent for auto attendance (#30846) 2022-04-29 16:37:26 +05:30
Deepesh Garg
2e62d518e8 fix: Multi currency opening invoices
(cherry picked from commit a8452c2ba2)
2022-04-29 09:46:34 +00:00
mergify[bot]
731107d0a3 test: create new item instead of using with _Test Item (backport #30827) (#30829)
This is an automatic backport of pull request #30827 done by [Mergify](https://mergify.com).


---


<details>
<summary>Mergify commands and options</summary>

<br />

More conditions and actions can be found in the [documentation](https://docs.mergify.com/).

You can also trigger Mergify actions by commenting on this pull request:

- `@Mergifyio refresh` will re-evaluate the rules
- `@Mergifyio rebase` will rebase this PR on its base branch
- `@Mergifyio update` will merge the base branch into this PR
- `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch

Additionally, on Mergify [dashboard](https://dashboard.mergify.com/) you can:

- look at your merge queues
- generate the Mergify configuration with the config editor.

Finally, you can contact us on https://mergify.com
</details>
2022-04-28 10:58:24 +00:00
mergify[bot]
645ee2d822 feat: support product bundles in picklist (backport #30762) (#30826)
* refactor: misc pick list refactors

- make tracking fields read only and no-copy 🤦
- collapse print settings section, most users configure it once and
  forget about it, not need to show this.
- call pick list grouping function directly
- use get_descendants_of instead of obscure db function

(cherry picked from commit 5c3f9019cc)

* test: bundles in picklist

(cherry picked from commit 7d5682020a)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py

* feat: Pick list from SO with Product Bundle

(cherry picked from commit 36c5e8a14f)

* refactor: sales order status update

- rename badly named variables
- support updated packed items

(cherry picked from commit e64cc66df7)

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

* perf: single update per Sales Order.

For each SO item the sales order picking status was being updated, this
isn't required and wasteful.

(cherry picked from commit c3fc0a4f55)

* feat: back-update min picked qty for a bundle

(cherry picked from commit 60bc26fdbe)

* refactor: simplify needlessly complicated code

(cherry picked from commit 3ddad6891a)

* refactor: groupby using keys instead of int index

(cherry picked from commit 277b51b404)

* refactor: simpler check for non-SO items

(cherry picked from commit f574121741)

* feat: create DN from pick list with bundle items

(cherry picked from commit 23cb0d684d)

* refactor: remove unnecssary vars

also remove misleading docstring

(cherry picked from commit 25485edfd9)

* fix: round off bundle qty

This is to accomodate bundles that might allow floating point qty.

(cherry picked from commit 41aa4b3524)

* feat: transfer picklist stock info to packing list

(cherry picked from commit 1ac275ce61)

* test: product bundle fixture

(cherry picked from commit ee54ece8fd)

* test: test bundle - picklist behaviour

(cherry picked from commit 9e60acdf56)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py

* fix: compare against stock qty while validating

Other changes:

- only allow whole number of bundles to get picked

(cherry picked from commit 8207697e43)

* fix: dont map picked qty and consider pick qty for new PL

Co-Authored-By: marination <maricadsouza221197@gmail.com>
(cherry picked from commit 47e1a0104c)

* fix(UX): only show pick list when picking is pending

[skip ci]

(cherry picked from commit 9a8e3ef235)

* chore: make picked qty read only

(cherry picked from commit ebd5f0b1bb)

* chore: conflicts and py3.7 compatibilty

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-27 17:08:37 +05:30
mergify[bot]
b45db347cc ci: failfast when merge conflict exists (#30823) (#30824)
[skip ci]

(cherry picked from commit 3ae9fa98c4)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-27 14:45:21 +05:30
Rucha Mahabal
3cdbb65b5a fix: filters not working in Shift Assignment Calendar view (#30822) 2022-04-27 14:44:25 +05:30
Saqib Ansari
f693d43cda Merge pull request #30809 from frappe/mergify/bp/version-13-hotfix/pr-30808
fix(pos): search field doesn't reset on checkout (backport #30808)
2022-04-27 14:15:07 +05:30
Ankush Menat
b33317e987 chore: format 2022-04-26 18:44:03 +05:30
Ankush Menat
980a87417b Merge pull request #30812 from frappe/mergify/bp/version-13-hotfix/pr-30811
chore: Warn users about multiple warehouse accounts (backport #30811)
2022-04-26 18:42:10 +05:30
marination
93482f3302 fix: Use account_type == 'Stock' to filter stock accounts
(cherry picked from commit e2a163d4e9)
2022-04-26 12:49:00 +00:00
marination
38fbd94ac9 chore: Warn users about multiple warehouse accounts
- Get distinct accounts that warehouse value has been booked against
- If same account as the one being set, ignore
- If not same account or multiple accounts: warn user that it makes it harder to track mismatches

(cherry picked from commit 44331f4f1f)
2022-04-26 12:49:00 +00:00
Saqib Ansari
b01f8555e5 fix(pos): number pad translations
(cherry picked from commit b1ac5ff9d2)
2022-04-26 09:18:11 +00:00
Saqib Ansari
edbf5513da fix(pos): search field doesn't reset on checkout
(cherry picked from commit 03a6103fe5)
2022-04-26 09:18:11 +00:00
Frappe PR Bot
b77b3780d6 chore(release): Bumped to Version 13.27.1
## [13.27.1](https://github.com/frappe/erpnext/compare/v13.27.0...v13.27.1) (2022-04-26)

### Bug Fixes

* Add accounting dimensions for round off GL Entry ([dedb90e](dedb90ea72))
* batch_no filtering not working when batch no is also a number in scientific notation ([#30770](https://github.com/frappe/erpnext/issues/30770)) ([#30771](https://github.com/frappe/erpnext/issues/30771)) ([c339305](c339305e9c))
* Check if accounting dimension exists ([1834671](1834671d59))
* dependent gle reposting (backport [#30726](https://github.com/frappe/erpnext/issues/30726)) ([#30772](https://github.com/frappe/erpnext/issues/30772)) ([a6d0938](a6d0938591))
* Do not validate while creating accounting dimension ([153b41a](153b41a269))
* e_commerce_settings.js ([3a5f5d5](3a5f5d5cd0))
* e_commerce_settings.py ([86c5f4d](86c5f4db85))
* First preference to parent cost center rather than round off cost center ([a2d95fc](a2d95fc62b))
* Handle Multiselect field mapping separately ([dc2f694](dc2f694547))
* **india:** 401 & 403 client error while generating IRN ([198bdcf](198bdcfdc6))
* **india:** cess value not considered while validating e-invoice totals ([#30800](https://github.com/frappe/erpnext/issues/30800)) ([f70fca1](f70fca1c9e))
* **india:** transporter name is null while generating e-way bill ([#30736](https://github.com/frappe/erpnext/issues/30736)) ([6291b28](6291b28c37))
* linter ([6dddbb9](6dddbb9f27))
* Loan doctypes in bank reconciliation ([e69c715](e69c71576d))
* Mistyped variable name in patch ([e76220e](e76220e819))
* monthly attendance sheet ([#30748](https://github.com/frappe/erpnext/issues/30748)) ([0b4e3f1](0b4e3f1467))
* Must not be able to start Job Card if it is related to Work Order that is not started yet ([#29072](https://github.com/frappe/erpnext/issues/29072)) ([#30755](https://github.com/frappe/erpnext/issues/30755)) ([b656ffa](b656ffa45e))
* Query filter fields from Website Item instead of Item master ([bed9e09](bed9e09153))
* select doctype as payment_document ([44f0b69](44f0b69152))
* **Selling,E-Commerce:** Shopping cart quotation without website item. ([#29085](https://github.com/frappe/erpnext/issues/29085)) ([ea0fe5e](ea0fe5e10c))
* SO analysis rpt will fetch SO's without Delivery note as well ([f9d89c7](f9d89c7ce6))
* translation ([#30781](https://github.com/frappe/erpnext/issues/30781)) ([#30783](https://github.com/frappe/erpnext/issues/30783)) ([8335ca6](8335ca6331))
* Update token to allow updates on protected branch ([baab379](baab3797ca))
* update translation ([#30725](https://github.com/frappe/erpnext/issues/30725)) ([#30776](https://github.com/frappe/erpnext/issues/30776)) ([b585262](b585262842))
* Use parent cost center for Sales and Purchase Invoice ([fe9f329](fe9f32946c))
* Use right precision for asset value after full schedule ([#30745](https://github.com/frappe/erpnext/issues/30745)) ([269e192](269e1923c9))
* Validate field filter wrt to Website Item & re-use validation in Item Group ([34437a8](34437a83df))
2022-04-26 07:52:13 +00:00
Deepesh Garg
016839e573 Merge pull request #30805 from frappe/version-13-pre-release
chore: version 13 release
2022-04-26 13:17:29 +05:30
Deepesh Garg
25bc10e0a9 Merge branch 'version-13' into version-13-pre-release 2022-04-26 12:32:06 +05:30
Deepesh Garg
87ec9cd0cc Merge pull request #30804 from frappe/version-13-hotfix
chore: version 13 pre release
2022-04-26 12:25:25 +05:30
Deepesh Garg
2f42cb6362 Merge branch 'version-13-pre-release' into version-13-hotfix 2022-04-26 11:47:34 +05:30
Deepesh Garg
30d1cecf60 Merge pull request #30801 from frappe/mergify/bp/version-13-hotfix/pr-30754
fix: First preference to parent cost center rather than round off cost center (backport #30754)
2022-04-25 17:28:55 +05:30
Deepesh Garg
4522826335 test: Unit test for round off entry dimensions
(cherry picked from commit 3fa1c63479)
2022-04-25 11:37:13 +00:00
Deepesh Garg
1834671d59 fix: Check if accounting dimension exists
(cherry picked from commit c312cd3725)
2022-04-25 11:37:13 +00:00
Deepesh Garg
dedb90ea72 fix: Add accounting dimensions for round off GL Entry
(cherry picked from commit 015812b0b8)
2022-04-25 11:37:12 +00:00
Deepesh Garg
fe9f32946c fix: Use parent cost center for Sales and Purchase Invoice
(cherry picked from commit c42547d40f)
2022-04-25 11:37:12 +00:00
Deepesh Garg
a2d95fc62b fix: First preference to parent cost center rather than round off cost center
(cherry picked from commit 0ac11a5b30)
2022-04-25 11:37:11 +00:00
mergify[bot]
f70fca1c9e fix(india): cess value not considered while validating e-invoice totals (#30800)
(cherry picked from commit 8e6c7a6bf7)

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2022-04-25 15:08:09 +05:30
Deepesh Garg
30bf48e1db Merge pull request #30798 from frappe/mergify/bp/version-13-hotfix/pr-30778
fix: Do not validate while creating accounting dimension (backport #30778)
2022-04-25 14:08:13 +05:30
Deepesh Garg
153b41a269 fix: Do not validate while creating accounting dimension
(cherry picked from commit 9bb132fdd3)
2022-04-25 07:01:28 +00:00
Deepesh Garg
d99cc8b05a Merge pull request #30794 from frappe/mergify/bp/version-13-hotfix/pr-30763
fix: Add loan doctypes in bank clearance (backport #30763)
2022-04-25 08:51:50 +05:30
Deepesh Garg
12f5d65271 test: Remove teardown method
(cherry picked from commit c515abc392)
2022-04-24 15:26:13 +00:00
Deepesh Garg
57485b30b8 test: Fixes in test case
(cherry picked from commit d4d83f4bb6)
2022-04-24 15:26:12 +00:00
Deepesh Garg
1a74c6ee56 test: Fixes in test case
(cherry picked from commit 0eacc99ab7)
2022-04-24 15:26:12 +00:00
Deepesh Garg
728ac1f54e test: Add test coverage for bank clearance
(cherry picked from commit 8a8476bb5c)
2022-04-24 15:26:12 +00:00
Deepesh Garg
44f0b69152 fix: select doctype as payment_document
(cherry picked from commit 3d0e68acaa)
2022-04-24 15:26:11 +00:00
Deepesh Garg
e69c71576d fix: Loan doctypes in bank reconciliation
(cherry picked from commit c3e27b5556)
2022-04-24 15:26:11 +00:00
Deepesh Garg
664a2989a6 Merge pull request #30773 from frappe/mergify/bp/version-13-hotfix/pr-30757
fix(india): 401 & 403 client error while generating IRN (backport #30757)
2022-04-22 19:47:19 +05:30
mergify[bot]
8335ca6331 fix: translation (#30781) (#30783)
fixed the short word for March in german language

(cherry picked from commit 43c1d63ab2)

Co-authored-by: Wolfram Schmidt <wolfram.schmidt@phamos.eu>
2022-04-22 17:39:15 +05:30
Devin Slauenwhite
ea0fe5e10c fix(Selling,E-Commerce): Shopping cart quotation without website item. (#29085)
* test: assert error if quotation contains non website item

* fix(test): validate exception without website item

* fix: shopping cart quotation without website item

* fix: sider issues

* fix: linter trilaing whitespace

* fix: linter format string after translation

* fix: Skip unpublished Variants with published templates in shopping cart quote validation

* style: Re-run pre-commit

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2022-04-22 17:19:29 +05:30
Saqib Ansari
6dddbb9f27 fix: linter 2022-04-22 14:15:21 +05:30
Deepesh Garg
a074d12afd Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30757 2022-04-22 13:20:09 +05:30
Deepesh Garg
712443a91c chore: Resolve conflicts 2022-04-22 11:48:07 +05:30
mergify[bot]
b585262842 fix: update translation (#30725) (#30776)
* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

* Update ru.csv

(cherry picked from commit e088e65871)

Co-authored-by: Vladislav <integration.into.society@gmail.com>
2022-04-22 11:18:29 +05:30
mergify[bot]
a6d0938591 fix: dependent gle reposting (backport #30726) (#30772)
* refactor: repost error handling

(cherry picked from commit afc5a55a23)

* test: dependent GL entry reposting

(cherry picked from commit a2af2daca7)

# Conflicts:
#	erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py

* fix: dependent GLE reposting

(cherry picked from commit ecdb49314f)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
#	erpnext/stock/stock_ledger.py

* test: repost queue progress

(cherry picked from commit 8f519545b0)

* test: use disposable item codes in tests

dependency causes flake

(cherry picked from commit d2882ea436)

* fix: correct sorting while updating bin

(cherry picked from commit b24920c0e9)

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

* fix: sort stock vouchers before reposting GLE

(cherry picked from commit 700e864d90)

* test: discard local future SLE cache between tests

(cherry picked from commit 9734329094)

* chore: conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-21 19:27:23 +05:30
Saqib Ansari
198bdcfdc6 fix(india): 401 & 403 client error while generating IRN
(cherry picked from commit ee8047aba3)

# Conflicts:
#	erpnext/regional/india/e_invoice/utils.py
2022-04-21 13:26:05 +00:00
Deepesh Garg
32cff94bde chore: Remove conflicts from Sales Order Analysis report (#30761)
* fix: Remove conflicts from Sales Order Analysis report

* fix: change field to duration and fetch elapsed seconds

* chore: Ignore linting check for sql query
2022-04-21 18:31:35 +05:30
mergify[bot]
c339305e9c fix: batch_no filtering not working when batch no is also a number in scientific notation (#30770) (#30771)
[skip ci]

(cherry picked from commit ee3036651a)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-21 16:39:24 +05:30
Marica
ac433dce2a Merge pull request #30179 from MostafaFekry/patch-4
fix: Filter fields in E Commerce Settings (use Website Item)
2022-04-21 14:36:20 +05:30
Marica
d8944b0499 Merge branch 'version-13-hotfix' into patch-4 2022-04-21 12:32:17 +05:30
marination
f58d3b4d3d test: setup e commerce settings before running invalid filtrs test 2022-04-21 12:31:16 +05:30
Ganga Manoj
269e1923c9 fix: Use right precision for asset value after full schedule (#30745) 2022-04-20 19:44:09 +05:30
marination
ba635145da test: Field filter validation and Custom field as field filter
- Test to block Item fields (which aren’t in Website Item) in E Commerce Settings as filters
- Removed unnecessary function and setup in E Commerce Settings test
- Removed commented useless test
- Test to check custom field as filter
2022-04-20 18:54:30 +05:30
marination
dc2f694547 fix: Handle Multiselect field mapping separately
- Map Multiselect child table to Website Item (copy rows)
2022-04-20 17:47:14 +05:30
mergify[bot]
b656ffa45e fix: Must not be able to start Job Card if it is related to Work Order that is not started yet (#29072) (#30755)
* fix: Cannot start Job strat if related to Work Order not started yet

* fix: Cannot start Job strat if related to Work Order not started yet

* test

* test

* fix siders

* PR review

* chore: Code cleanup

- Better short circuit for if condition (make it such that both conditions dont always have to be computed)
- Remove `r.message` extraction by avoiding `then()`

* chore: Remove unnecessary json change

Co-authored-by: marination <maricadsouza221197@gmail.com>
(cherry picked from commit 143786aaa0)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2022-04-20 15:11:11 +05:30
Raffael Meyer
0b4e3f1467 fix: monthly attendance sheet (#30748) 2022-04-20 12:29:45 +05:30
marination
e76220e819 fix: Mistyped variable name in patch 2022-04-20 12:27:04 +05:30
mergify[bot]
77e8c542dd chore: Update creds to allow updates on protected branch (#30749) (#30750)
(cherry picked from commit e4265ce814)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2022-04-19 17:33:15 +05:30
FinByz Tech Pvt. Ltd
6291b28c37 fix(india): transporter name is null while generating e-way bill (#30736) 2022-04-19 16:26:56 +05:30
semantic-release-bot
4389100fa1 chore(release): Bumped to Version 13.27.0
# [13.27.0](https://github.com/frappe/erpnext/compare/v13.26.0...v13.27.0) (2022-04-19)

### Bug Fixes

* Depreciation Amount calculation for first row when Monthly Depreciation is allowed ([#30692](https://github.com/frappe/erpnext/issues/30692)) ([9cf790d](9cf790db9d))
* Do not show disabled dimensions in reports ([46d773d](46d773df5d))
* Exchange gain and loss on advance jv allocation ([9c5f841](9c5f8415bf))
* Map correct company to PO made via Prod Plan (subcontract) ([44be67e](44be67e6dd))
* Map Production Plan company in subassembly WO created from it ([f604101](f604101fea))
* **patch:** check if column is present while fixing reverse linking (backport [#30737](https://github.com/frappe/erpnext/issues/30737)) ([#30739](https://github.com/frappe/erpnext/issues/30739)) ([38002e3](38002e3fe2))
* Payment reco query with max invocie and payment amount limit ([4008c95](4008c95ac6))
* Price changing on creating Sales retrun from Delivery Note ([93fe840](93fe840844))
* process statement to_date override ([378d15d](378d15d388))
* Remove "Values Out of Sync" validation (backport [#30707](https://github.com/frappe/erpnext/issues/30707)) ([#30718](https://github.com/frappe/erpnext/issues/30718)) ([fb9d640](fb9d640d8b))
* Update token to allow updates on protected branch ([2a29aef](2a29aefb19))
* update translation ([#30716](https://github.com/frappe/erpnext/issues/30716)) ([fb127da](fb127da489))

### Features

* Item-wise provisional accounting for service items ([5776881](5776881f34))
2022-04-19 10:46:00 +00:00
Ankush Menat
f266d765fd chore: add GH token 2022-04-19 15:58:49 +05:30
Deepesh Garg
5915a73939 Merge pull request #30741 from frappe/version-13-pre-release
chore: release for v13.27.0
2022-04-19 15:42:46 +05:30
Deepesh Garg
f6898344ef Merge pull request #30744 from frappe/mergify/bp/version-13-pre-release/pr-30742
fix: Update token to allow updates on protected branch (backport #30742)
2022-04-19 15:33:21 +05:30
Deepesh Garg
2a29aefb19 fix: Update token to allow updates on protected branch
(cherry picked from commit 6f332f3669)
2022-04-19 10:02:33 +00:00
Deepesh Garg
377c37a99f Merge pull request #30743 from frappe/mergify/bp/version-13-hotfix/pr-30742
fix: Update token to allow updates on protected branch (backport #30742)
2022-04-19 15:32:10 +05:30
Deepesh Garg
baab3797ca fix: Update token to allow updates on protected branch
(cherry picked from commit 6f332f3669)
2022-04-19 10:01:58 +00:00
Deepesh Garg
8c291f7b7a Merge pull request #30734 from frappe/mergify/bp/version-13-hotfix/pr-30721
fix: SO's without delivery note will also be fetched (backport #30721)
2022-04-19 14:12:03 +05:30
Deepesh Garg
381e9d2236 Merge pull request #30740 from frappe/version-13-hotfix
chore: Pre-release for v13.27.0
2022-04-19 14:10:58 +05:30
mergify[bot]
38002e3fe2 fix(patch): check if column is present while fixing reverse linking (backport #30737) (#30739)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-04-19 12:39:28 +05:30
marination
9506dbe433 chore: Patch to copy custom fields (field filters) from Item to Website Item 2022-04-18 21:38:22 +05:30
ruthra kumar
61ff1c22b3 test: Sales order analysis report
(cherry picked from commit 13487e2408)
2022-04-18 14:29:06 +00:00
ruthra kumar
f9d89c7ce6 fix: SO analysis rpt will fetch SO's without Delivery note as well
(cherry picked from commit e28e6726f1)

# Conflicts:
#	erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
2022-04-18 14:29:06 +00:00
marination
34437a83df fix: Validate field filter wrt to Website Item & re-use validation in Item Group 2022-04-18 19:24:08 +05:30
marination
bed9e09153 fix: Query filter fields from Website Item instead of Item master
- tweak `filters.py` to correctly query filter field values from Website Item
- Use Website Item for filter field options in Settings and Item Group Field Filter table
2022-04-18 19:23:17 +05:30
Marica
952e267e92 Merge branch 'version-13-hotfix' into patch-4 2022-04-18 18:10:02 +05:30
mergify[bot]
8e30af84cd chore: Add semantic releases (backport #30729) (#30732)
* chore: Add sematic releases

(cherry picked from commit 41249c57c4)

* chore: Update branch name

(cherry picked from commit cc1bdd426b)

* chore: block major releases

(cherry picked from commit c12a36aed9)

* ci: use latest ubuntu container

(cherry picked from commit 6fc11cb4c5)

* chore: do not publish any assets

(cherry picked from commit e0a9a69d76)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2022-04-18 17:18:53 +05:30
Deepesh Garg
d6126b6695 Merge pull request #30731 from frappe/mergify/bp/version-13-hotfix/pr-30728
fix: Price changing on creating Sales return from Delivery Note (backport #30728)
2022-04-18 16:33:14 +05:30
Deepesh Garg
93fe840844 fix: Price changing on creating Sales retrun from Delivery Note
(cherry picked from commit 9c081947ec)
2022-04-18 09:04:50 +00:00
Deepesh Garg
183ff53ccb Merge pull request #30720 from frappe/mergify/bp/version-13-hotfix/pr-30716
fix: update translation (backport #30716)
2022-04-15 14:01:19 +05:30
HENRY Florian
fb127da489 fix: update translation (#30716)
* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

(cherry picked from commit e6aa28ea14)
2022-04-14 16:07:40 +00:00
Deepesh Garg
933bd7413a Merge pull request #30408 from bhavesh95863/patch-3
fix: process statement to_date override
2022-04-14 18:43:28 +05:30
mergify[bot]
fb9d640d8b fix: Remove "Values Out of Sync" validation (backport #30707) (#30718)
This is an automatic backport of pull request #30707 done by [Mergify](https://mergify.com).
Cherry-pick of 89fab78027 has failed:
```
On branch mergify/bp/version-13-hotfix/pr-30707
Your branch is up to date with 'origin/version-13-hotfix'.

You are currently cherry-picking commit 89fab78027.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   erpnext/accounts/doctype/journal_entry/journal_entry.py

no changes added to commit (use "git add" and/or "git commit -a")
```


To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

---


<details>
<summary>Mergify commands and options</summary>

<br />

More conditions and actions can be found in the [documentation](https://docs.mergify.com/).

You can also trigger Mergify actions by commenting on this pull request:

- `@Mergifyio refresh` will re-evaluate the rules
- `@Mergifyio rebase` will rebase this PR on its base branch
- `@Mergifyio update` will merge the base branch into this PR
- `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch

Additionally, on Mergify [dashboard](https://dashboard.mergify.com/) you can:

- look at your merge queues
- generate the Mergify configuration with the config editor.

Finally, you can contact us on https://mergify.com
</details>
2022-04-14 12:29:35 +00:00
Deepesh Garg
62bad90c90 Merge pull request #30717 from frappe/mergify/bp/version-13-hotfix/pr-30710
fix: Exchange gain and loss on advance Journal Entry allocation (backport #30710)
2022-04-14 15:29:55 +05:30
Deepesh Garg
a167f9e17f test: Update customer in Sales Invoice
(cherry picked from commit c38be53ce8)
2022-04-14 09:40:53 +00:00
Deepesh Garg
e135c452af test: Update customer
(cherry picked from commit 972d9ec5b4)
2022-04-14 09:40:53 +00:00
Deepesh Garg
9c5f8415bf fix: Exchange gain and loss on advance jv allocation
(cherry picked from commit 31883b699d)
2022-04-14 09:40:53 +00:00
Deepesh Garg
46a00d1da6 Merge pull request #30714 from frappe/mergify/bp/version-13-hotfix/pr-30675
feat: Item-wise provisional accounting for service items (backport #30675)
2022-04-14 13:14:48 +05:30
Ankush Menat
3a7ac29907 chore: check in missing __init__.py (#30713) 2022-04-14 13:03:34 +05:30
Deepesh Garg
3dcef9352e test: Update test case
(cherry picked from commit ad171c6225)
2022-04-14 07:01:23 +00:00
Deepesh Garg
5776881f34 feat: Item-wise provisional accounting for service items
(cherry picked from commit 3ce64170db)
2022-04-14 07:01:23 +00:00
Deepesh Garg
0b62784f6f Merge pull request #30700 from deepeshgarg007/pay_reco_query_v13
fix: Payment reco query with max invoice and payment amount limit
2022-04-14 11:11:30 +05:30
Ankush Menat
ef4eb4705e chore: warning for DATEV deprecation (#30703) 2022-04-13 15:07:48 +05:30
Ganga Manoj
9cf790db9d fix: Depreciation Amount calculation for first row when Monthly Depreciation is allowed (#30692) 2022-04-13 14:15:10 +05:30
mergify[bot]
2778123106 Merge pull request #30699 from frappe/mergify/bp/version-13-hotfix/pr-30688
fix: Map Production Plan company in subassembly WO created from it (backport #30688)
2022-04-13 07:30:31 +00:00
marination
44be67e6dd fix: Map correct company to PO made via Prod Plan (subcontract)
(cherry picked from commit 6315acc450)
2022-04-13 07:14:16 +00:00
marination
f604101fea fix: Map Production Plan company in subassembly WO created from it
(cherry picked from commit 2777c5c67c)
2022-04-13 07:14:15 +00:00
Deepesh Garg
4008c95ac6 fix: Payment reco query with max invocie and payment amount limit 2022-04-13 12:21:33 +05:30
Deepesh Garg
512b480f6a chore: version bump 2022-04-12 23:31:47 +05:30
Deepesh Garg
d5d6b34380 Merge pull request #30695 from deepeshgarg007/v13_26_0
chore: change log and version bump
2022-04-12 23:29:44 +05:30
Deepesh Garg
2c2d5ccb8f Merge pull request #30694 from frappe/version-13-pre-release
chore: Release for v13.26.0
2022-04-12 23:28:24 +05:30
Deepesh Garg
147499bc9c chore: change log and version bump 2022-04-12 23:01:09 +05:30
Deepesh Garg
dc669b540d Merge pull request #30691 from frappe/mergify/bp/version-13-hotfix/pr-30689
fix: Do not show disabled dimensions in reports (backport #30689)
2022-04-12 22:28:46 +05:30
Deepesh Garg
08e2b9110c Merge pull request #30693 from frappe/version-13-hotfix
chore: Pre release for v13.26.0
2022-04-12 16:56:52 +05:30
Deepesh Garg
46d773df5d fix: Do not show disabled dimensions in reports
(cherry picked from commit 9a1c560c82)
2022-04-12 10:41:25 +00:00
Deepesh Garg
4a48a6aae3 Merge pull request #30690 from frappe/mergify/bp/version-13-hotfix/pr-30686
feat: Ignore permlevel for specific fields (backport #30686)
2022-04-12 16:10:51 +05:30
Nabin Hait
e650d99cdd feat: Ignore permlevel for specific fields
(cherry picked from commit 993c6c0de9)
2022-04-12 10:26:17 +00:00
Deepesh Garg
23ccadbcae Merge pull request #30683 from frappe/mergify/bp/version-13-hotfix/pr-30651
fix: Download JSON for GSTR-1 report (backport #30651)
2022-04-12 14:00:55 +05:30
Deepesh Garg
bfd8ab6d72 Merge pull request #30687 from frappe/mergify/bp/version-13-hotfix/pr-30626
feat(india): e-invoicing for intra-state union territory transactions (backport #30626)
2022-04-12 14:00:38 +05:30
Saqib Ansari
2ab431ad36 feat(india): e-invoicing for intra-state union territory transactions
(cherry picked from commit 45fca6bed7)
2022-04-12 08:03:23 +00:00
mergify[bot]
21066a48b6 fix: ignore item-less maintenance visit for sr no (#30684) (#30685)
(cherry picked from commit 60fb71bd2a)

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

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

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

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

(cherry picked from commit 49560d20bc)

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

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

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

(cherry picked from commit 4895761d89)

* chore: fix translation csv file

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

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

(cherry picked from commit e4c6d6a1a6)

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

* fix: resolve conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 13:20:36 +05:30
ruthra kumar
39f8ee2ea6 test: added test cases for group filters
(cherry picked from commit 16bfb930f8)
2022-04-07 07:32:04 +00:00
ruthra kumar
a9d5000eab refactor: use group fields from Sales Order and Sales Order Items
(cherry picked from commit b2ed9fd3fe)
2022-04-07 07:32:03 +00:00
ruthra kumar
00acb000dc refactor: item filters are linked with group filters
(cherry picked from commit e324d668d3)
2022-04-07 07:32:02 +00:00
ruthra kumar
9336a3413f refactor: adding new filters and column to test cases
(cherry picked from commit 7558f1b078)
2022-04-07 07:32:02 +00:00
ruthra kumar
c6f946e3e8 feat: additional filters in payment terms status report
(cherry picked from commit eaeadbc422)
2022-04-07 07:32:01 +00:00
Saqib Ansari
d720d15e3d fix(pos): reload doc before set value (#30610) 2022-04-06 22:32:23 +05:30
Saqib Ansari
2e29cf38bb Merge pull request #30611 from nextchamp-saqib/fix-pos-patch-v13
fix(pos): reload doc before set value
2022-04-06 22:28:49 +05:30
Saqib Ansari
f94afc1fff fix(pos): reload doc before set value 2022-04-06 22:14:37 +05:30
Ankush Menat
f89c1b2c0c fix: dont trigger closed WO check on new Job card 2022-04-06 18:28:54 +05:30
mergify[bot]
52aa3561e3 fix: hide pending qty only if original item is assigned (#30599) (#30600)
(cherry picked from commit 8b090a9f7d)

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

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 15:14:37 +05:30
Mostafa Fekry
86c5f4db85 fix: e_commerce_settings.py
Update get meta from Item to Website Item doctype to check validate_field_filters
2022-03-28 18:15:46 +05:30
Ankush Menat
fc5fd34621 Merge branch 'version-13-hotfix' into patch-4 2022-03-28 18:06:50 +05:30
Bhavesh Maheshwari
378d15d388 fix: process statement to_date override 2022-03-25 11:27:19 +05:30
Mostafa Fekry
3a5f5d5cd0 fix: e_commerce_settings.js
Update get meta from Item to Website Item
To allow Website Item Fields to display meta fields
2022-03-10 16:05:08 +02:00
138 changed files with 3492 additions and 1338 deletions

View File

@@ -2,6 +2,13 @@
set -e
# Check for merge conflicts before proceeding
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
cd ~ || exit
sudo apt-get install redis-server libcups2-dev

31
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Generate Semantic Release
on:
push:
branches:
- version-13
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
uses: actions/setup-node@v2
with:
node-version: 14
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
- name: Create Release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GIT_AUTHOR_NAME: "Frappe PR Bot"
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release

24
.releaserc Normal file
View File

@@ -0,0 +1,24 @@
{
"branches": ["version-13"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"breaking": true, "release": false}
]
},
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
}
],
[
"@semantic-release/git", {
"assets": ["erpnext/__init__.py"],
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}

View File

@@ -4,7 +4,8 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = '13.25.2'
__version__ = "13.28.0"
def get_default_company(user=None):
"""Get default company for user"""

View File

@@ -386,7 +386,6 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
end_date,
@@ -570,7 +569,6 @@ def book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
posting_date,
@@ -591,6 +589,7 @@ def book_revenue_via_journal_entry(
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
"account": credit_account,

View File

@@ -99,7 +99,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
create_custom_field(doctype, df)
create_custom_field(doctype, df, ignore_validate=True)
count += 1
@@ -115,7 +115,7 @@ def add_dimension_to_budget_doctype(df, doc):
}
)
create_custom_field("Budget", df)
create_custom_field("Budget", df, ignore_validate=True)
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
@@ -205,10 +205,16 @@ def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes")
def get_accounting_dimensions(as_list=True):
def get_accounting_dimensions(as_list=True, filters=None):
if not filters:
filters = {"disabled": 0}
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all(
"Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]
"Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"],
filters=filters,
)
if as_list:

View File

@@ -5,7 +5,10 @@
import frappe
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.utils import flt, fmt_money, getdate, nowdate
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, getdate
import erpnext
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
@@ -76,6 +79,52 @@ class BankClearance(Document):
as_dict=1,
)
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
loan_disbursements = (
frappe.qb.from_(loan_disbursement)
.select(
ConstantColumn("Loan Disbursement").as_("payment_document"),
loan_disbursement.name.as_("payment_entry"),
loan_disbursement.disbursed_amount.as_("credit"),
ConstantColumn(0).as_("debit"),
loan_disbursement.reference_number.as_("cheque_number"),
loan_disbursement.reference_date.as_("cheque_date"),
loan_disbursement.disbursement_date.as_("posting_date"),
loan_disbursement.applicant.as_("against_account"),
)
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.disbursement_date >= self.from_date)
.where(loan_disbursement.disbursement_date <= self.to_date)
.where(loan_disbursement.clearance_date.isnull())
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
.orderby(loan_disbursement.disbursement_date)
.orderby(loan_disbursement.name, frappe.qb.desc)
).run(as_dict=1)
loan_repayment = frappe.qb.DocType("Loan Repayment")
loan_repayments = (
frappe.qb.from_(loan_repayment)
.select(
ConstantColumn("Loan Repayment").as_("payment_document"),
loan_repayment.name.as_("payment_entry"),
loan_repayment.amount_paid.as_("debit"),
ConstantColumn(0).as_("credit"),
loan_repayment.reference_number.as_("cheque_number"),
loan_repayment.reference_date.as_("cheque_date"),
loan_repayment.applicant.as_("against_account"),
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.posting_date >= self.from_date)
.where(loan_repayment.posting_date <= self.to_date)
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
.orderby(loan_repayment.posting_date)
.orderby(loan_repayment.name, frappe.qb.desc)
).run(as_dict=1)
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
pos_sales_invoices = frappe.db.sql(
@@ -114,20 +163,29 @@ class BankClearance(Document):
entries = sorted(
list(payment_entries)
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
key=lambda k: k["posting_date"] or getdate(nowdate()),
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
+ list(loan_disbursements)
+ list(loan_repayments),
key=lambda k: getdate(k["posting_date"]),
)
self.set("payment_entries", [])
self.total_amount = 0.0
default_currency = erpnext.get_default_currency()
for d in entries:
row = self.append("payment_entries", {})
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
if not d.get("account_currency"):
d.account_currency = default_currency
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
d.posting_date = getdate(d.posting_date)
d.pop("credit")
d.pop("debit")

View File

@@ -1,9 +1,96 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
import frappe
from frappe.utils import add_months, getdate
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.loan_management.doctype.loan.test_loan import (
create_loan,
create_loan_accounts,
create_loan_type,
create_repayment_entry,
make_loan_disbursement_entry,
)
class TestBankClearance(unittest.TestCase):
pass
@classmethod
def setUpClass(cls):
make_bank_account()
create_loan_accounts()
create_loan_masters()
add_transactions()
# Basic test case to test if bank clearance tool doesn't break
# Detailed test can be added later
def test_bank_clearance(self):
bank_clearance = frappe.get_doc("Bank Clearance")
bank_clearance.account = "_Test Bank Clearance - _TC"
bank_clearance.from_date = add_months(getdate(), -1)
bank_clearance.to_date = getdate()
bank_clearance.get_payment_entries()
self.assertEqual(len(bank_clearance.payment_entries), 3)
def make_bank_account():
if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"account_type": "Bank",
"account_name": "_Test Bank Clearance",
"company": "_Test Company",
"parent_account": "Bank Accounts - _TC",
}
).insert()
def create_loan_masters():
create_loan_type(
"Clearance Loan",
2000000,
13.5,
25,
0,
5,
"Cash",
"_Test Bank Clearance - _TC",
"_Test Bank Clearance - _TC",
"Loan Account - _TC",
"Interest Income Account - _TC",
"Penalty Income Account - _TC",
)
def add_transactions():
make_payment_entry()
make_loan()
def make_loan():
loan = create_loan(
"_Test Customer",
"Clearance Loan",
280000,
"Repay Over Number of Periods",
20,
applicant_type="Customer",
)
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
repayment_entry.save()
repayment_entry.submit()
def make_payment_entry():
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()

View File

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

View File

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

View File

@@ -19,7 +19,6 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import (
check_if_stock_and_account_balance_synced,
get_account_currency,
get_balance_on,
get_stock_accounts,
@@ -88,9 +87,6 @@ class JournalEntry(AccountsController):
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
check_if_stock_and_account_balance_synced(
self.posting_date, self.company, self.doctype, self.name
)
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries

View File

@@ -114,10 +114,13 @@ class OpeningInvoiceCreationTool(Document):
)
or {}
)
default_currency = frappe.db.get_value(row.party_type, row.party, "default_currency")
if company_details:
invoice.update(
{
"currency": company_details.get("default_currency"),
"currency": default_currency or company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head"),
}
)

View File

@@ -226,10 +226,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.total_allocated_amount > party_amount)));
frm.toggle_display("set_exchange_gain_loss",
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
((frm.doc.paid_from_account_currency != company_currency ||
frm.doc.paid_to_account_currency != company_currency) &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
frm.refresh_fields();
},

View File

@@ -38,6 +38,15 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
]
};
});
this.frm.set_query("cost_center", () => {
return {
"filters": {
"company": this.frm.doc.company,
"is_group": 0
}
}
});
},
refresh: function() {

View File

@@ -24,6 +24,7 @@
"invoice_limit",
"payment_limit",
"bank_cash_account",
"cost_center",
"sec_break1",
"invoices",
"column_break_15",
@@ -178,13 +179,19 @@
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"links": [],
"modified": "2021-10-04 20:27:11.114194",
"modified": "2022-04-29 15:37:10.246831",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
@@ -209,5 +216,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -332,6 +332,9 @@ 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):
condition = " and cost_center = '{0}' ".format(self.cost_center)
if get_invoices:
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
@@ -350,9 +353,13 @@ class PaymentReconciliation(Document):
)
if self.minimum_invoice_amount:
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_invoice_amount))
condition += " and {dr_or_cr} >= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.minimum_invoice_amount)
)
if self.maximum_invoice_amount:
condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_invoice_amount))
condition += " and {dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
)
elif get_return_invoices:
condition = " and doc.company = '{0}' ".format(self.company)
@@ -367,15 +374,19 @@ class PaymentReconciliation(Document):
else ""
)
dr_or_cr = (
"gl.debit_in_account_currency"
"debit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "gl.credit_in_account_currency"
else "credit_in_account_currency"
)
if self.minimum_invoice_amount:
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_payment_amount))
condition += " and gl.{dr_or_cr} >= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
)
if self.maximum_invoice_amount:
condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_payment_amount))
condition += " and gl.{dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
)
else:
condition += (

View File

@@ -1,9 +1,96 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
import frappe
from frappe.utils import add_days, getdate
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPaymentReconciliation(unittest.TestCase):
pass
@classmethod
def setUpClass(cls):
make_customer()
make_invoice_and_payment()
def test_payment_reconciliation(self):
payment_reco = frappe.get_doc("Payment Reconciliation")
payment_reco.company = "_Test Company"
payment_reco.party_type = "Customer"
payment_reco.party = "_Test Payment Reco Customer"
payment_reco.receivable_payable_account = "Debtors - _TC"
payment_reco.from_invoice_date = add_days(getdate(), -1)
payment_reco.to_invoice_date = getdate()
payment_reco.from_payment_date = add_days(getdate(), -1)
payment_reco.to_payment_date = getdate()
payment_reco.maximum_invoice_amount = 1000
payment_reco.maximum_payment_amount = 1000
payment_reco.invoice_limit = 10
payment_reco.payment_limit = 10
payment_reco.bank_cash_account = "_Test Bank - _TC"
payment_reco.cost_center = "_Test Cost Center - _TC"
payment_reco.get_unreconciled_entries()
self.assertEqual(len(payment_reco.get("invoices")), 1)
self.assertEqual(len(payment_reco.get("payments")), 1)
payment_entry = payment_reco.get("payments")[0].reference_name
invoice = payment_reco.get("invoices")[0].invoice_number
payment_reco.allocate_entries(
{
"payments": [payment_reco.get("payments")[0].as_dict()],
"invoices": [payment_reco.get("invoices")[0].as_dict()],
}
)
payment_reco.reconcile()
payment_entry_doc = frappe.get_doc("Payment Entry", payment_entry)
self.assertEqual(payment_entry_doc.get("references")[0].reference_name, invoice)
def make_customer():
if not frappe.db.get_value("Customer", "_Test Payment Reco Customer"):
frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "_Test Payment Reco Customer",
"customer_type": "Individual",
"customer_group": "_Test Customer Group",
"territory": "_Test Territory",
}
).insert()
def make_invoice_and_payment():
si = create_sales_invoice(
customer="_Test Payment Reco Customer", qty=1, rate=690, do_not_save=True
)
si.cost_center = "_Test Cost Center - _TC"
si.save()
si.submit()
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
"payment_type": "Receive",
"party_type": "Customer",
"party": "_Test Payment Reco Customer",
"company": "_Test Company",
"paid_from_account_currency": "INR",
"paid_to_account_currency": "INR",
"source_exchange_rate": 1,
"target_exchange_rate": 1,
"reference_no": "1",
"reference_date": getdate(),
"received_amount": 690,
"paid_amount": 690,
"paid_from": "Debtors - _TC",
"paid_to": "_Test Bank - _TC",
"cost_center": "_Test Cost Center - _TC",
}
)
pe.insert()
pe.submit()

View File

@@ -166,7 +166,7 @@ class PeriodClosingVoucher(AccountsController):
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.report_type = 'Profit and Loss'
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s
group by t1.account, {dimension_fields}

View File

@@ -11,7 +11,7 @@ from erpnext.accounts.deferred_revenue import (
convert_deferred_expense_to_expense,
convert_deferred_revenue_to_income,
)
from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.accounts.general_ledger import make_gl_entries
class ProcessDeferredAccounting(Document):
@@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
)
make_reverse_gl_entries(gl_entries=gl_entries)
make_gl_entries(gl_entries=gl_entries, cancel=1)

View File

@@ -34,8 +34,9 @@ class ProcessStatementOfAccounts(Document):
frappe.throw(_("Customers not selected."))
if self.enable_auto_email:
self.to_date = self.start_date
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
if self.start_date and getdate(self.start_date) >= getdate(today()):
self.to_date = self.start_date
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
def get_report_pdf(doc, consolidated=True):

View File

@@ -30,6 +30,9 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
onload: function() {
this._super();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
if(!this.frm.doc.supplier && this.frm.doc.credit_to) {

View File

@@ -801,7 +801,9 @@ class PurchaseInvoice(BuyingController):
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
provisional_account = self.get_company_default("default_provisional_account")
provisional_account = frappe.db.get_value(
"Purchase Receipt Item", item.pr_detail, "provisional_expense_account"
) or self.get_company_default("default_provisional_account")
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
if not purchase_receipt_doc:
@@ -824,7 +826,7 @@ class PurchaseInvoice(BuyingController):
if expense_booked_in_pr:
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(
item, gl_entries, self.posting_date, reverse=1
item, gl_entries, self.posting_date, provisional_account, reverse=1
)
if not self.is_internal_transfer():
@@ -1315,7 +1317,9 @@ class PurchaseInvoice(BuyingController):
if (
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
self.company, "Purchase Invoice", self.name
)
gl_entries.append(
self.get_gl_dict(

View File

@@ -1449,7 +1449,8 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
def test_provisional_accounting_entry(self):
item = create_item("_Test Non Stock Item", is_stock_item=0)
create_item("_Test Non Stock Item", is_stock_item=0)
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
@@ -1472,6 +1473,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.save()
pi.submit()
self.assertEquals(pr.items[0].provisional_expense_account, "Provision Account - _TC")
# Check GLE for Purchase Invoice
expected_gle = [
["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, -1)],

View File

@@ -34,7 +34,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
var me = this;
this._super();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry'];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
this.frm.set_df_property("debit_to", "print_hide", 0);

View File

@@ -1473,7 +1473,9 @@ class SalesInvoice(SellingController):
and self.base_rounding_adjustment
and not self.is_internal_transfer()
):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
self.company, "Sales Invoice", self.name
)
gl_entries.append(
self.get_gl_dict(

View File

@@ -2007,6 +2007,13 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_rounding_adjustment_3(self):
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
create_dimension()
si = create_sales_invoice(do_not_save=True)
si.items = []
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
@@ -2034,6 +2041,10 @@ class TestSalesInvoice(unittest.TestCase):
"included_in_print_rate": 1,
},
)
si.cost_center = "_Test Cost Center 2 - _TC"
si.location = "Block 1"
si.save()
si.submit()
self.assertEqual(si.net_total, 4007.16)
@@ -2069,6 +2080,18 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(debit_credit_diff, 0)
round_off_gle = frappe.db.get_value(
"GL Entry",
{"voucher_type": "Sales Invoice", "voucher_no": si.name, "account": "Round Off - _TC"},
["cost_center", "location"],
as_dict=1,
)
self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
self.assertEqual(round_off_gle.location, "Block 1")
disable_dimension()
def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
@@ -2270,6 +2293,14 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def test_deferred_revenue_missing_account(self):
si = create_sales_invoice(posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
self.assertRaises(frappe.ValidationError, si.save)
def test_fixed_deferred_revenue(self):
deferred_account = create_account(
account_name="Deferred Revenue",
@@ -3042,7 +3073,7 @@ class TestSalesInvoice(unittest.TestCase):
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.submit_journal_entries = 0
acc_settings.save()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
@@ -3054,6 +3085,62 @@ class TestSalesInvoice(unittest.TestCase):
si.reload()
self.assertTrue(si.items[0].serial_no)
def test_gain_loss_with_advance_entry(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
unlink_enabled = frappe.db.get_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
)
frappe.db.set_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1
)
jv = make_journal_entry("_Test Receivable USD - _TC", "_Test Bank - _TC", -7000, save=False)
jv.accounts[0].exchange_rate = 70
jv.accounts[0].credit_in_account_currency = 100
jv.accounts[0].party_type = "Customer"
jv.accounts[0].party = "_Test Customer USD"
jv.save()
jv.submit()
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=75,
do_not_save=1,
rate=100,
)
si.append(
"advances",
{
"reference_type": "Journal Entry",
"reference_name": jv.name,
"reference_row": jv.accounts[0].name,
"advance_amount": 100,
"allocated_amount": 100,
"ref_exchange_rate": 70,
},
)
si.save()
si.submit()
expected_gle = [
["_Test Receivable USD - _TC", 7500.0, 500],
["Exchange Gain/Loss - _TC", 500.0, 0.0],
["Sales - _TC", 0.0, 7500.0],
]
check_gl_entries(self, si.name, expected_gle, nowdate())
frappe.db.set_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()

View File

@@ -273,7 +273,7 @@ def round_off_debit_credit(gl_map):
def make_round_off_gle(gl_map, debit_credit_diff, precision):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
gl_map[0].company
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
)
round_off_account_exists = False
round_off_gle = frappe._dict()
@@ -310,14 +310,43 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
}
)
update_accounting_dimensions(round_off_gle)
if not round_off_account_exists:
gl_map.append(round_off_gle)
def get_round_off_account_and_cost_center(company):
def update_accounting_dimensions(round_off_gle):
dimensions = get_accounting_dimensions()
meta = frappe.get_meta(round_off_gle["voucher_type"])
has_all_dimensions = True
for dimension in dimensions:
if not meta.has_field(dimension):
has_all_dimensions = False
if dimensions and has_all_dimensions:
dimension_values = frappe.db.get_value(
round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
)
for dimension in dimensions:
round_off_gle[dimension] = dimension_values.get(dimension)
def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
round_off_account, round_off_cost_center = frappe.get_cached_value(
"Company", company, ["round_off_account", "round_off_cost_center"]
) or [None, None]
meta = frappe.get_meta(voucher_type)
# Give first preference to parent cost center for round off GLE
if meta.has_field("cost_center"):
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
if parent_cost_center:
round_off_cost_center = parent_cost_center
if not round_off_account:
frappe.throw(_("Please mention Round Off Account in Company"))

View File

@@ -53,6 +53,22 @@ frappe.query_reports["Accounts Payable"] = {
}
}
},
{
"fieldname": "party_account",
"label": __("Payable Account"),
"fieldtype": "Link",
"options": "Account",
get_query: () => {
var company = frappe.query_report.get_filter_value('company');
return {
filters: {
'company': company,
'account_type': 'Payable',
'is_group': 0
}
};
}
},
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),

View File

@@ -66,6 +66,22 @@ frappe.query_reports["Accounts Receivable"] = {
}
}
},
{
"fieldname": "party_account",
"label": __("Receivable Account"),
"fieldtype": "Link",
"options": "Account",
get_query: () => {
var company = frappe.query_report.get_filter_value('company');
return {
filters: {
'company': company,
'account_type': 'Receivable',
'is_group': 0
}
};
}
},
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),

View File

@@ -111,6 +111,7 @@ class ReceivablePayableReport(object):
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
party=gle.party,
party_account=gle.account,
posting_date=gle.posting_date,
account_currency=gle.account_currency,
remarks=gle.remarks if self.filters.get("show_remarks") else None,
@@ -777,18 +778,21 @@ class ReceivablePayableReport(object):
conditions.append("party=%s")
values.append(self.filters.get(party_type_field))
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
accounts = [
d.name
for d in frappe.get_all(
"Account", filters={"account_type": account_type, "company": self.filters.company}
)
]
if accounts:
conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
values += accounts
if self.filters.party_account:
conditions.append("account =%s")
values.append(self.filters.party_account)
else:
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
accounts = [
d.name
for d in frappe.get_all(
"Account", filters={"account_type": account_type, "company": self.filters.company}
)
]
if accounts:
conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
values += accounts
def add_customer_filters(self, conditions, values):
if self.filters.get("customer_group"):
@@ -889,6 +893,14 @@ class ReceivablePayableReport(object):
width=180,
)
self.add_column(
label="Receivable Account" if self.party_type == "Customer" else "Payable Account",
fieldname="party_account",
fieldtype="Link",
options="Account",
width=180,
)
if self.party_naming_by == "Naming Series":
self.add_column(
_("{0} Name").format(self.party_type),

View File

@@ -50,12 +50,19 @@ class TestAccountsReceivable(unittest.TestCase):
make_credit_note(name)
report = execute(filters)
expected_data_after_credit_note = [100, 0, 0, 40, -40]
expected_data_after_credit_note = [100, 0, 0, 40, -40, "Debtors - _TC2"]
row = report[1][0]
self.assertEqual(
expected_data_after_credit_note,
[row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding],
[
row.invoice_grand_total,
row.invoiced,
row.paid,
row.credit_note,
row.outstanding,
row.party_account,
],
)

View File

@@ -124,11 +124,10 @@ def get_columns(invoice_list, additional_table_columns):
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
]
expense_accounts = (
tax_accounts
) = (
expense_columns
) = tax_columns = unrealized_profit_loss_accounts = unrealized_profit_loss_account_columns = []
expense_accounts = []
tax_accounts = []
unrealized_profit_loss_accounts = []
if invoice_list:
expense_accounts = frappe.db.sql_list(
@@ -163,10 +162,11 @@ def get_columns(invoice_list, additional_table_columns):
unrealized_profit_loss_account_columns = [
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
]
for account in tax_accounts:
if account not in expense_accounts:
tax_columns.append(account + ":Currency/currency:120")
tax_columns = [
(account + ":Currency/currency:120")
for account in tax_accounts
if account not in expense_accounts
]
columns = (
columns

View File

@@ -1,10 +1,17 @@
import unittest
import frappe
from frappe.test_runner import make_test_objects
from erpnext.accounts.party import get_party_shipping_address
from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries
from erpnext.accounts.utils import (
get_future_stock_vouchers,
get_voucherwise_gl_entries,
sort_stock_vouchers_by_posting_date,
)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestUtils(unittest.TestCase):
@@ -47,6 +54,25 @@ class TestUtils(unittest.TestCase):
msg="get_voucherwise_gl_entries not returning expected GLes",
)
def test_stock_voucher_sorting(self):
vouchers = []
item = make_item().name
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
for doc in (se1, se2, se3):
vouchers.append((doc.doctype, doc.name))
vouchers.append(("Stock Entry", "Wat"))
sorted_vouchers = sort_stock_vouchers_by_posting_date(list(reversed(vouchers)))
self.assertEqual(sorted_vouchers, vouchers)
ADDRESS_RECORDS = [
{

View File

@@ -3,6 +3,7 @@
from json import loads
from typing import List, Tuple
import frappe
import frappe.defaults
@@ -19,10 +20,6 @@ from erpnext.stock import get_warehouse_account_map
from erpnext.stock.utils import get_stock_value_on
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError):
pass
class FiscalYearError(frappe.ValidationError):
pass
@@ -1127,6 +1124,9 @@ def update_gl_entries_after(
def repost_gle_for_stock_vouchers(
stock_vouchers, posting_date, company=None, warehouse_account=None
):
if not stock_vouchers:
return
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql(
"""delete from `tabGL Entry`
@@ -1134,6 +1134,8 @@ def repost_gle_for_stock_vouchers(
(voucher_type, voucher_no),
)
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
@@ -1154,6 +1156,27 @@ def repost_gle_for_stock_vouchers(
_delete_gl_entries(voucher_type, voucher_no)
def sort_stock_vouchers_by_posting_date(
stock_vouchers: List[Tuple[str, str]]
) -> List[Tuple[str, str]]:
sle = frappe.qb.DocType("Stock Ledger Entry")
voucher_nos = [v[1] for v in stock_vouchers]
sles = (
frappe.qb.from_(sle)
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
.groupby(sle.voucher_type, sle.voucher_no)
).run(as_dict=True)
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
unknown_vouchers = set(stock_vouchers) - set(sorted_vouchers)
if unknown_vouchers:
sorted_vouchers.extend(unknown_vouchers)
return sorted_vouchers
def get_future_stock_vouchers(
posting_date, posting_time, for_warehouses=None, for_items=None, company=None
):
@@ -1247,47 +1270,6 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
return matched
def check_if_stock_and_account_balance_synced(
posting_date, company, voucher_type=None, voucher_no=None
):
if not cint(erpnext.is_perpetual_inventory_enabled(company)):
return
accounts = get_stock_accounts(company, voucher_type, voucher_no)
stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
for account in accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
account, posting_date, company
)
if abs(account_bal - stock_bal) > 0.1:
precision = get_field_precision(
frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value("Company", company, "default_currency"),
)
diff = flt(stock_bal - account_bal, precision)
error_reason = _(
"Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}."
).format(stock_bal, account_bal, frappe.bold(account), posting_date)
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}").format(
frappe.bold(diff), frappe.bold(posting_date)
)
frappe.msgprint(
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
title=_("Values Out Of Sync"),
primary_action={
"label": _("Make Journal Entry"),
"client_action": "erpnext.route_to_adjustment_jv",
"args": get_journal_entry(account, stock_adjustment_account, diff),
},
)
def get_stock_accounts(company, voucher_type=None, voucher_no=None):
stock_accounts = [
d.name

View File

@@ -364,7 +364,7 @@ class Asset(AccountsController):
if has_pro_rata and n == 0:
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + 1
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
@@ -628,7 +628,7 @@ class Asset(AccountsController):
asset_value_after_full_schedule = flt(
flt(self.gross_purchase_amount) - flt(accumulated_depreciation_after_full_schedule),
self.precision("gross_purchase_amount"),
row.precision("expected_value_after_useful_life"),
)
if (

View File

@@ -23,6 +23,10 @@
"order_confirmation_no",
"order_confirmation_date",
"amended_from",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"drop_ship",
"customer",
"customer_name",
@@ -1138,16 +1142,39 @@
"fieldtype": "Link",
"label": "Tax Withholding Category",
"options": "Tax Withholding Category"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions "
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2021-09-28 13:10:47.955401",
"modified": "2022-04-26 12:16:38.694276",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -1194,6 +1221,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1

View File

@@ -0,0 +1,31 @@
## Version 13.26.0 Release Notes
### Features & Enhancements
- feat: Receivable/Payable Account column and filter in AR/AP report ([#30620](https://github.com/frappe/erpnext/pull/30620))
- feat(india): e-invoicing for intra-state union territory transactions ([#30626](https://github.com/frappe/erpnext/pull/30626))
- feat: Ignore permlevel for specific fields ([#30686](https://github.com/frappe/erpnext/pull/30686))
- feat: 'customer' column and more filter to Payment terms status report ([#30499](https://github.com/frappe/erpnext/pull/30499))
### Fixes
- fix: fallback to item_name if description is not found ([#30619](https://github.com/frappe/erpnext/pull/30619))
- fix: Deferred Revenue/Expense Account validation ([#30602](https://github.com/frappe/erpnext/pull/30602))
- fix(pos): reload doc before set value ([#30610](https://github.com/frappe/erpnext/pull/30610))
- fix: Exchange gain and loss button in Payment Entry ([#30606](https://github.com/frappe/erpnext/pull/30606))
- fix(pos): cannot change paid amount in pos payments ([#30657](https://github.com/frappe/erpnext/pull/30657))
- fix: hide pending qty only if original item is assigned ([#30599](https://github.com/frappe/erpnext/pull/30599))
- fix(pos): reload doc before set value ([#30611](https://github.com/frappe/erpnext/pull/30611))
- fix: Handle multiple item transfer in separate SEs against WO ([#30674](https://github.com/frappe/erpnext/pull/30674))
- fix(patch): check null values in is_cancelled patch ([#30594](https://github.com/frappe/erpnext/pull/30594))
- fix: Download JSON for GSTR-1 report ([#30651](https://github.com/frappe/erpnext/pull/30651))
- fix: remove bad defaults from BOM operation ([#30644](https://github.com/frappe/erpnext/pull/30644))
- fix: update translation ([#30474](https://github.com/frappe/erpnext/pull/30474))
- fix: dont reassign mutable (list) to a different field ([#30634](https://github.com/frappe/erpnext/pull/30634))
- fix: update translation ([#30654](https://github.com/frappe/erpnext/pull/30654))
- fix: ignore item-less maintenance visit for sr no ([#30684](https://github.com/frappe/erpnext/pull/30684))
- fix: removed unused courses template ([#30596](https://github.com/frappe/erpnext/pull/30596))
- fix: Implicit ignore pricing rule check on returns ([#30662](https://github.com/frappe/erpnext/pull/30662))
- fix: warehouse naming when suffix is present ([#30621](https://github.com/frappe/erpnext/pull/30621))
- fix: Ignore disabled tax categories ([#30542](https://github.com/frappe/erpnext/pull/30542))

View File

@@ -181,6 +181,7 @@ class AccountsController(TransactionBase):
else:
self.validate_deferred_start_and_end_date()
self.validate_deferred_income_expense_account()
self.set_inter_company_account()
if self.doctype == "Purchase Invoice":
@@ -209,6 +210,27 @@ class AccountsController(TransactionBase):
(self.doctype, self.name),
)
def validate_deferred_income_expense_account(self):
field_map = {
"Sales Invoice": "deferred_revenue_account",
"Purchase Invoice": "deferred_expense_account",
}
for item in self.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
if not item.get(field_map.get(self.doctype)):
default_deferred_account = frappe.db.get_value(
"Company", self.company, "default_" + field_map.get(self.doctype)
)
if not default_deferred_account:
frappe.throw(
_(
"Row #{0}: Please update deferred revenue/expense account in item row or default account in company master"
).format(item.idx)
)
else:
item.set(field_map.get(self.doctype), default_deferred_account)
def validate_deferred_start_and_end_date(self):
for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
@@ -1978,12 +2000,13 @@ def get_advance_journal_entries(
reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
# nosemgrep
journal_entries = frappe.db.sql(
"""
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
t2.reference_name as against_order
t2.reference_name as against_order, t2.exchange_rate
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where

View File

@@ -22,6 +22,9 @@ class QtyMismatchError(ValidationError):
class BuyingController(StockController, Subcontracting):
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
def get_feed(self):
if self.get("supplier_name"):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)

View File

@@ -330,7 +330,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc = frappe.get_doc(target)
doc.is_return = 1
doc.return_against = source.name
doc.ignore_pricing_rule = 1
doc.set_warehouse = ""
if doctype == "Sales Invoice" or doctype == "POS Invoice":
doc.is_pos = source.is_pos

View File

@@ -16,6 +16,9 @@ from erpnext.stock.utils import get_incoming_rate
class SellingController(StockController):
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"]
def get_feed(self):
return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)

View File

@@ -24,17 +24,17 @@ frappe.ui.form.on("E Commerce Settings", {
);
}
frappe.model.with_doctype("Item", () => {
frappe.model.with_doctype("Website Item", () => {
const web_item_meta = frappe.get_meta('Website Item');
const valid_fields = web_item_meta.fields.filter(
df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
).map(df => ({ label: df.label, value: df.fieldname }));
frm.fields_dict.filter_fields.grid.update_docfield_property(
frm.get_field("filter_fields").grid.update_docfield_property(
'fieldname', 'fieldtype', 'Select'
);
frm.fields_dict.filter_fields.grid.update_docfield_property(
frm.get_field("filter_fields").grid.update_docfield_property(
'fieldname', 'options', valid_fields
);
});

View File

@@ -26,7 +26,7 @@ class ECommerceSettings(Document):
self.is_redisearch_loaded = is_search_module_loaded()
def validate(self):
self.validate_field_filters()
self.validate_field_filters(self.filter_fields, self.enable_field_filters)
self.validate_attribute_filters()
self.validate_checkout()
self.validate_search_index_fields()
@@ -50,21 +50,22 @@ class ECommerceSettings(Document):
define_autocomplete_dictionary()
create_website_items_index()
def validate_field_filters(self):
if not (self.enable_field_filters and self.filter_fields):
@staticmethod
def validate_field_filters(filter_fields, enable_field_filters):
if not (enable_field_filters and filter_fields):
return
item_meta = frappe.get_meta("Item")
web_item_meta = frappe.get_meta("Website Item")
valid_fields = [
df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
]
for f in self.filter_fields:
if f.fieldname not in valid_fields:
for row in filter_fields:
if row.fieldname not in valid_fields:
frappe.throw(
_(
"Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type 'Link' or 'Table MultiSelect'"
).format(f.idx, f.fieldname)
"Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'"
).format(row.idx, frappe.bold(row.fieldname))
)
def validate_attribute_filters(self):

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
@@ -11,42 +11,34 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
class TestECommerceSettings(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
def get_cart_settings(self):
return frappe.get_doc({"doctype": "E Commerce Settings", "company": "_Test Company"})
# NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
# We aren't checking just currency exchange record anymore
# while validating price list currency exchange rate to that of company.
# The API is being used to fetch the rate which again almost always
# gives back a valid value (for valid currencies).
# This makes the test obsolete.
# Commenting because im not sure if there's a better test we can write
# def test_exchange_rate_exists(self):
# frappe.db.sql("""delete from `tabCurrency Exchange`""")
# cart_settings = self.get_cart_settings()
# cart_settings.price_list = "_Test Price List Rest of the World"
# self.assertRaises(ShoppingCartSetupError, cart_settings.validate_price_list_exchange_rate)
# from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
# currency_exchange_records
# frappe.get_doc(currency_exchange_records[0]).insert()
# cart_settings.validate_price_list_exchange_rate()
def tearDown(self):
frappe.db.rollback()
def test_tax_rule_validation(self):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
cart_settings = self.get_cart_settings()
cart_settings = frappe.get_doc("E Commerce Settings")
cart_settings.enabled = 1
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule)
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
def test_invalid_filter_fields(self):
"Check if Item fields are blocked in E Commerce Settings filter fields."
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
setup_e_commerce_settings({"enable_field_filters": 1})
create_custom_field(
"Item",
dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"),
)
settings = frappe.get_doc("E Commerce Settings")
settings.append("filter_fields", {"fieldname": "test_data"})
self.assertRaises(frappe.ValidationError, settings.save)
def setup_e_commerce_settings(values_dict):
"Accepts a dict of values that updates E Commerce Settings."

View File

@@ -22,12 +22,14 @@ class ProductFiltersBuilder:
fields, filter_data = [], []
filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
# filter valid field filters i.e. those that exist in Item
item_meta = frappe.get_meta("Item", cached=True)
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
# filter valid field filters i.e. those that exist in Website Item
web_item_meta = frappe.get_meta("Website Item", cached=True)
fields = [
web_item_meta.get_field(field) for field in filter_fields if web_item_meta.has_field(field)
]
for df in fields:
item_filters, item_or_filters = {"published_in_website": 1}, []
item_filters, item_or_filters = {"published": 1}, []
link_doctype_values = self.get_filtered_link_doctype_records(df)
if df.fieldtype == "Link":
@@ -50,9 +52,13 @@ class ProductFiltersBuilder:
]
)
# exclude variants if mentioned in settings
if frappe.db.get_single_value("E Commerce Settings", "hide_variants"):
item_filters["variant_of"] = ["is", "not set"]
# Get link field values attached to published items
item_values = frappe.get_all(
"Item",
"Website Item",
fields=[df.fieldname],
filters=item_filters,
or_filters=item_or_filters,

View File

@@ -277,6 +277,54 @@ class TestProductDataEngine(unittest.TestCase):
# tear down
setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0})
def test_custom_field_as_filter(self):
"Test if custom field functions as filter correctly."
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
create_custom_field(
"Website Item",
dict(
owner="Administrator",
fieldname="supplier",
label="Supplier",
fieldtype="Link",
options="Supplier",
insert_after="on_backorder",
),
)
frappe.db.set_value(
"Website Item", {"item_code": "Test 11I Laptop"}, "supplier", "_Test Supplier"
)
frappe.db.set_value(
"Website Item", {"item_code": "Test 12I Laptop"}, "supplier", "_Test Supplier 1"
)
settings = frappe.get_doc("E Commerce Settings")
settings.append("filter_fields", {"fieldname": "supplier"})
settings.save()
filter_engine = ProductFiltersBuilder()
field_filters = filter_engine.get_field_filters()
custom_filter = field_filters[1]
filter_values = custom_filter[1]
self.assertEqual(custom_filter[0].options, "Supplier")
self.assertEqual(len(filter_values), 2)
self.assertIn("_Test Supplier", filter_values)
# test if custom filter works in query
field_filters = {"supplier": "_Test Supplier 1"}
engine = ProductQuery()
result = engine.query(
attributes={}, fields=field_filters, search_term=None, start=0, item_group=None
)
items = result.get("items")
# check if only 'Raw Material' are fetched in the right order
self.assertEqual(len(items), 1)
self.assertEqual(items[0].get("item_code"), "Test 12I Laptop")
def create_variant_web_item():
"Create Variant and Template Website Items."

View File

@@ -37,7 +37,7 @@ frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
}
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce_integrations' target='_blank'>Ecommerce Integrations</a>"
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
})

View File

@@ -2,7 +2,6 @@
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:healthcare_service_unit_name",
"beta": 1,
"creation": "2016-09-21 13:48:14.731437",
"description": "Healthcare Service Unit",
@@ -207,7 +206,7 @@
],
"is_tree": 1,
"links": [],
"modified": "2021-08-19 14:09:11.643464",
"modified": "2022-04-07 03:11:36.023277",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit",

View File

@@ -592,6 +592,9 @@ accounting_dimension_doctypes = [
"Subscription Plan",
"POS Invoice",
"POS Invoice Item",
"Purchase Order",
"Purchase Receipt",
"Sales Order",
]
regional_overrides = {

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, get_datetime
from frappe.utils import cint, get_datetime, get_link_to_form
from erpnext.hr.doctype.shift_assignment.shift_assignment import (
get_actual_start_end_datetime_of_shift,
@@ -127,19 +127,17 @@ def mark_attendance_and_link_log(
log_names = [x.name for x in logs]
employee = logs[0].employee
if attendance_status == "Skip":
frappe.db.sql(
"""update `tabEmployee Checkin`
set skip_auto_attendance = %s
where name in %s""",
("1", log_names),
)
skip_attendance_in_checkins(log_names)
return None
elif attendance_status in ("Present", "Absent", "Half Day"):
employee_doc = frappe.get_doc("Employee", employee)
if not frappe.db.exists(
duplicate = frappe.db.exists(
"Attendance",
{"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
):
)
if not duplicate:
doc_dict = {
"doctype": "Attendance",
"employee": employee,
@@ -155,6 +153,12 @@ def mark_attendance_and_link_log(
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
if attendance_status == "Absent":
attendance.add_comment(
text=_("Employee was marked Absent for not meeting the working hours threshold.")
)
frappe.db.sql(
"""update `tabEmployee Checkin`
set attendance = %s
@@ -163,12 +167,10 @@ def mark_attendance_and_link_log(
)
return attendance
else:
frappe.db.sql(
"""update `tabEmployee Checkin`
set skip_auto_attendance = %s
where name in %s""",
("1", log_names),
)
skip_attendance_in_checkins(log_names)
if duplicate:
add_comment_in_checkins(log_names, duplicate)
return None
else:
frappe.throw(_("{} is an invalid Attendance Status.").format(attendance_status))
@@ -237,3 +239,29 @@ def time_diff_in_hours(start, end):
def find_index_in_dict(dict_list, key, value):
return next((index for (index, d) in enumerate(dict_list) if d[key] == value), None)
def add_comment_in_checkins(log_names, duplicate):
text = _("Auto Attendance skipped due to duplicate attendance record: {}").format(
get_link_to_form("Attendance", duplicate)
)
for name in log_names:
frappe.get_doc(
{
"doctype": "Comment",
"comment_type": "Comment",
"reference_doctype": "Employee Checkin",
"reference_name": name,
"content": text,
}
).insert(ignore_permissions=True)
def skip_attendance_in_checkins(log_names):
EmployeeCheckin = frappe.qb.DocType("Employee Checkin")
(
frappe.qb.update(EmployeeCheckin)
.set("skip_auto_attendance", 1)
.where(EmployeeCheckin.name.isin(log_names))
).run()

View File

@@ -34,6 +34,15 @@ frappe.ui.form.on("Leave Allocation", {
});
}
}
// make new leaves allocated field read only if allocation is created via leave policy assignment
// and leave type is earned leave, since these leaves would be allocated via the scheduler
if (frm.doc.leave_policy_assignment) {
frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => {
if (r && cint(r.is_earned_leave))
frm.set_df_property("new_leaves_allocated", "read_only", 1);
});
}
},
expire_allocation: function(frm) {

View File

@@ -237,7 +237,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-10-01 15:28:26.335104",
"modified": "2022-04-07 09:50:33.145825",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
@@ -278,5 +278,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "employee"
"timeline_field": "employee",
"title_field": "employee_name",
"track_changes": 1
}

View File

@@ -84,7 +84,7 @@ class ShiftAssignment(Document):
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
from frappe.desk.calendar import get_event_conditions
employee = frappe.db.get_value(
"Employee", {"user_id": frappe.session.user}, ["name", "company"], as_dict=True
@@ -95,20 +95,22 @@ def get_events(start, end, filters=None):
employee = ""
company = frappe.db.get_value("Global Defaults", None, "default_company")
from frappe.desk.reportview import get_filters_cond
conditions = get_filters_cond("Shift Assignment", filters, [])
add_assignments(events, start, end, conditions=conditions)
conditions = get_event_conditions("Shift Assignment", filters)
events = add_assignments(start, end, conditions=conditions)
return events
def add_assignments(events, start, end, conditions=None):
def add_assignments(start, end, conditions=None):
events = []
query = """select name, start_date, end_date, employee_name,
employee, docstatus, shift_type
from `tabShift Assignment` where
start_date >= %(start_date)s
or end_date <= %(end_date)s
or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date)
(
start_date >= %(start_date)s
or end_date <= %(end_date)s
or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date)
)
and docstatus = 1"""
if conditions:
query += conditions

View File

@@ -4,14 +4,22 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, nowdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_events
test_dependencies = ["Shift Type"]
class TestShiftAssignment(unittest.TestCase):
class TestShiftAssignment(FrappeTestCase):
def setUp(self):
frappe.db.sql("delete from `tabShift Assignment`")
frappe.db.delete("Shift Assignment")
if not frappe.db.exists("Shift Type", "Day Shift"):
frappe.get_doc(
{"doctype": "Shift Type", "name": "Day Shift", "start_time": "9:00:00", "end_time": "18:00:00"}
).insert()
def test_make_shift_assignment(self):
shift_assignment = frappe.get_doc(
@@ -86,3 +94,36 @@ class TestShiftAssignment(unittest.TestCase):
)
self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
def test_shift_assignment_calendar(self):
employee1 = make_employee("test_shift_assignment1@example.com", company="_Test Company")
employee2 = make_employee("test_shift_assignment2@example.com", company="_Test Company")
date = nowdate()
shift_1 = frappe.get_doc(
{
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": employee1,
"start_date": date,
"status": "Active",
}
).submit()
frappe.get_doc(
{
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": employee2,
"start_date": date,
"status": "Active",
}
).submit()
events = get_events(
start=date, end=date, filters=[["Shift Assignment", "employee", "=", employee1, False]]
)
self.assertEqual(len(events), 1)
self.assertEqual(events[0]["name"], shift_1.name)

View File

@@ -139,7 +139,17 @@ class ShiftType(Document):
for date in dates:
shift_details = get_employee_shift(employee, date, True)
if shift_details and shift_details.shift_type.name == self.name:
mark_attendance(employee, date, "Absent", self.name)
attendance = mark_attendance(employee, date, "Absent", self.name)
if attendance:
frappe.get_doc(
{
"doctype": "Comment",
"comment_type": "Comment",
"reference_doctype": "Attendance",
"reference_name": attendance,
"content": frappe._("Employee was marked Absent due to missing Employee Checkins."),
}
).insert(ignore_permissions=True)
def get_assigned_employee(self, from_date=None, consider_default_shift=False):
filters = {"start_date": (">", from_date), "shift_type": self.name, "docstatus": "1"}

View File

@@ -57,11 +57,10 @@ def execute(filters=None):
data = []
leave_list = None
leave_types = None
if filters.summarized_view:
leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
leave_list = [d[0] + ":Float:120" for d in leave_types]
columns.extend(leave_list)
leave_types = frappe.get_all("Leave Type", pluck="name")
columns.extend([leave_type + ":Float:120" for leave_type in leave_types])
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
if filters.group_by:
@@ -81,13 +80,19 @@ def execute(filters=None):
holiday_map,
conditions,
default_holiday_list,
leave_list=leave_list,
leave_types=leave_types,
)
emp_att_map.update(emp_att_data)
data += record
else:
record, emp_att_map = add_data(
emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list
emp_map,
att_map,
filters,
holiday_map,
conditions,
default_holiday_list,
leave_types=leave_types,
)
data += record
@@ -104,12 +109,10 @@ def get_chart_data(emp_att_map, days):
{"name": "Leave", "values": []},
]
for idx, day in enumerate(days, start=0):
p = day.replace("::65", "")
labels.append(day.replace("::65", ""))
total_absent_on_day = 0
total_leave_on_day = 0
total_present_on_day = 0
total_holiday = 0
for emp in emp_att_map.keys():
if emp_att_map[emp][idx]:
if emp_att_map[emp][idx] == "A":
@@ -134,9 +137,8 @@ def get_chart_data(emp_att_map, days):
def add_data(
employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=None
employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None
):
record = []
emp_att_map = {}
for emp in employee_map:
@@ -222,7 +224,7 @@ def add_data(
else:
leaves[d.leave_type] = d.count
for d in leave_list:
for d in leave_types:
if d in leaves:
row.append(leaves[d])
else:

View File

@@ -489,6 +489,17 @@ def update_previous_leave_allocation(
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
if e_leave_type.based_on_date_of_joining:
text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
)
else:
text = _("allocated {0} leave(s) via scheduler on {1}").format(
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
)
allocation.add_comment(comment_type="Info", text=text)
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
earned_leaves = 0.0

View File

@@ -12,6 +12,9 @@ frappe.ui.form.on('Maintenance Visit', {
// filters for serial no based on item code
if (frm.doc.maintenance_type === "Scheduled") {
let item_code = frm.doc.purposes[0].item_code;
if (!item_code) {
return;
}
frappe.call({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule",
args: {

View File

@@ -100,7 +100,6 @@
"read_only": 1
},
{
"default": "5",
"depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
@@ -178,7 +177,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-10 06:19:08.462027",
"modified": "2022-04-08 01:18:33.547481",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",

View File

@@ -28,12 +28,12 @@ frappe.ui.form.on('Job Card', {
frappe.flags.resume_job = 0;
let has_items = frm.doc.items && frm.doc.items.length;
if (frm.doc.__onload.work_order_stopped) {
if (!frm.is_new() && frm.doc.__onload.work_order_stopped) {
frm.disable_save();
return;
}
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
@@ -73,7 +73,18 @@ frappe.ui.form.on('Job Card', {
if (frm.doc.docstatus == 0 && !frm.is_new() &&
(frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
// if Job Card is link to Work Order, the job card must not be able to start if Work Order not "Started"
// and if stock mvt for WIP is required
if (frm.doc.work_order) {
frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => {
if (result.skip_transfer === 1 || result.status == 'In Process') {
frm.trigger("prepare_timer_buttons");
}
});
} else {
frm.trigger("prepare_timer_buttons");
}
}
frm.trigger("setup_quality_inspection");

View File

@@ -462,6 +462,7 @@ class ProductionPlan(Document):
work_order_data = {
"wip_warehouse": default_warehouses.get("wip_warehouse"),
"fg_warehouse": default_warehouses.get("fg_warehouse"),
"company": self.get("company"),
}
self.prepare_data_for_sub_assembly_items(row, work_order_data)
@@ -499,6 +500,7 @@ class ProductionPlan(Document):
for supplier, po_list in subcontracted_po.items():
po = frappe.new_doc("Purchase Order")
po.company = self.company
po.supplier = supplier
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
po.is_subcontracted = "Yes"

View File

@@ -1100,6 +1100,56 @@ class TestWorkOrder(FrappeTestCase):
for index, row in enumerate(ste_manu.get("items"), start=1):
self.assertEqual(index, row.idx)
@change_settings(
"Manufacturing Settings",
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
)
def test_work_order_multiple_material_transfer(self):
"""
Test transferring multiple RMs in separate Stock Entries.
"""
work_order = make_wo_order_test_record(planned_start_date=now(), qty=1)
test_stock_entry.make_stock_entry( # stock up RM
item_code="_Test Item",
target="_Test Warehouse - _TC",
qty=1,
basic_rate=5000.0,
)
test_stock_entry.make_stock_entry( # stock up RM
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=2,
basic_rate=1000.0,
)
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1)
)
del transfer_entry.get("items")[0] # transfer only one RM
transfer_entry.submit()
# WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending
work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(work_order.required_items[0].transferred_qty, 0)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
final_transfer_entry = frappe.get_doc( # transfer last RM with For Quantity = 0
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0)
)
final_transfer_entry.save()
self.assertEqual(final_transfer_entry.fg_completed_qty, 0.0)
self.assertEqual(final_transfer_entry.items[0].qty, 1)
final_transfer_entry.submit()
work_order.reload()
# WO's "Material Transferred for Mfg" shows all is transferred, no RM is pending
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(work_order.required_items[0].transferred_qty, 1)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
def update_job_card(job_card, jc_qty=None):
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")

View File

@@ -540,8 +540,10 @@ erpnext.work_order = {
|| frm.doc.transfer_material_against == 'Job Card') ? 0 : 1;
if (show_start_btn) {
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') {
let pending_to_transfer = frm.doc.required_items.some(
item => flt(item.transferred_qty) < flt(item.required_qty)
);
if (pending_to_transfer && frm.doc.status != 'Stopped') {
frm.has_start_btn = true;
frm.add_custom_button(__('Create Pick List'), function() {
erpnext.work_order.create_pick_list(frm);

View File

@@ -1174,7 +1174,11 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.from_bom = 1
stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty))
# accept 0 qty as well
stock_entry.fg_completed_qty = (
qty if qty is not None else (flt(work_order.qty) - flt(work_order.produced_qty))
)
if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value(
"BOM", work_order.bom_no, "inspection_required"

View File

@@ -350,6 +350,7 @@ erpnext.patches.v13_0.enable_provisional_accounting
erpnext.patches.v13_0.update_disbursement_account
erpnext.patches.v13_0.update_reserved_qty_closed_wo
erpnext.patches.v13_0.amazon_mws_deprecation_warning
erpnext.patches.v13_0.datev_deprecation_warning
erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
erpnext.patches.v13_0.update_accounts_in_loan_docs
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
@@ -357,4 +358,8 @@ erpnext.patches.v13_0.rename_non_profit_fields
erpnext.patches.v13_0.enable_ksa_vat_docs #1
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
erpnext.patches.v13_0.education_deprecation_warning
erpnext.patches.v13_0.create_accounting_dimensions_in_orders

View File

@@ -20,7 +20,7 @@ def execute():
"""
UPDATE `tab{doctype}`
SET is_cancelled = 0
where is_cancelled in ('', NULL, 'No')""".format(
where is_cancelled in ('', 'No') or is_cancelled is NULL""".format(
doctype=doctype
)
)

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
# Erase all default item manufacturers that dont exist.
item = frappe.qb.DocType("Item")
manufacturer = frappe.qb.DocType("Manufacturer")
(
frappe.qb.update(item)
.set(item.default_item_manufacturer, None)
.left_join(manufacturer)
.on(item.default_item_manufacturer == manufacturer.name)
.where(manufacturer.name.isnull() & item.default_item_manufacturer.isnotnull())
).run()

View File

@@ -0,0 +1,94 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
"Add Field Filters, that are not standard fields in Website Item, as Custom Fields."
def move_table_multiselect_data(docfield):
"Copy child table data (Table Multiselect) from Item to Website Item for a docfield."
table_multiselect_data = get_table_multiselect_data(docfield)
field = docfield.fieldname
for row in table_multiselect_data:
# add copied multiselect data rows in Website Item
web_item = frappe.db.get_value("Website Item", {"item_code": row.parent})
web_item_doc = frappe.get_doc("Website Item", web_item)
child_doc = frappe.new_doc(docfield.options, web_item_doc, field)
for field in ["name", "creation", "modified", "idx"]:
row[field] = None
child_doc.update(row)
child_doc.parenttype = "Website Item"
child_doc.parent = web_item
child_doc.insert()
def get_table_multiselect_data(docfield):
child_table = frappe.qb.DocType(docfield.options)
item = frappe.qb.DocType("Item")
table_multiselect_data = ( # query table data for field
frappe.qb.from_(child_table)
.join(item)
.on(item.item_code == child_table.parent)
.select(child_table.star)
.where((child_table.parentfield == docfield.fieldname) & (item.published_in_website == 1))
).run(as_dict=True)
return table_multiselect_data
settings = frappe.get_doc("E Commerce Settings")
if not (settings.enable_field_filters or settings.filter_fields):
return
item_meta = frappe.get_meta("Item")
valid_item_fields = [
df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
]
web_item_meta = frappe.get_meta("Website Item")
valid_web_item_fields = [
df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
]
for row in settings.filter_fields:
# skip if illegal field
if row.fieldname not in valid_item_fields:
continue
# if Item field is not in Website Item, add it as a custom field
if row.fieldname not in valid_web_item_fields:
df = item_meta.get_field(row.fieldname)
create_custom_field(
"Website Item",
dict(
owner="Administrator",
fieldname=df.fieldname,
label=df.label,
fieldtype=df.fieldtype,
options=df.options,
description=df.description,
read_only=df.read_only,
no_copy=df.no_copy,
insert_after="on_backorder",
),
)
# map field values
if df.fieldtype == "Table MultiSelect":
move_table_multiselect_data(df)
else:
frappe.db.sql( # nosemgrep
"""
UPDATE `tabWebsite Item` wi, `tabItem` i
SET wi.{0} = i.{0}
WHERE wi.item_code = i.item_code
""".format(
row.fieldname
)
)

View File

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

View File

@@ -0,0 +1,9 @@
import click
def execute():
click.secho(
"DATEV reports are moved to a separate app and will be removed from ERPNext in version-14.\n"
"Please install the app to continue using them: https://github.com/alyf-de/erpnext_datev",
fg="yellow",
)

View File

@@ -0,0 +1,10 @@
import click
def execute():
click.secho(
"Education Domain is moved to a separate app and will be removed from ERPNext in version-14.\n"
"When upgrading to ERPNext version-14, please install the app to continue using the Education domain: https://github.com/frappe/education",
fg="yellow",
)

View File

@@ -10,54 +10,58 @@ def execute():
frappe.reload_doc("hr", "doctype", "Leave Encashment")
additional_salaries = frappe.get_all(
"Additional Salary",
fields=["name", "salary_slip", "type", "salary_component"],
filters={"salary_slip": ["!=", ""]},
group_by="salary_slip",
)
leave_encashments = frappe.get_all(
"Leave Encashment",
fields=["name", "additional_salary"],
filters={"additional_salary": ["!=", ""]},
)
employee_incentives = frappe.get_all(
"Employee Incentive",
fields=["name", "additional_salary"],
filters={"additional_salary": ["!=", ""]},
)
for incentive in employee_incentives:
frappe.db.sql(
""" UPDATE `tabAdditional Salary`
SET ref_doctype = 'Employee Incentive', ref_docname = %s
WHERE name = %s
""",
(incentive["name"], incentive["additional_salary"]),
if frappe.db.has_column("Leave Encashment", "additional_salary"):
leave_encashments = frappe.get_all(
"Leave Encashment",
fields=["name", "additional_salary"],
filters={"additional_salary": ["!=", ""]},
)
for leave_encashment in leave_encashments:
frappe.db.sql(
""" UPDATE `tabAdditional Salary`
SET ref_doctype = 'Leave Encashment', ref_docname = %s
WHERE name = %s
""",
(leave_encashment["name"], leave_encashment["additional_salary"]),
)
salary_slips = [sal["salary_slip"] for sal in additional_salaries]
for salary in additional_salaries:
comp_type = "earnings" if salary["type"] == "Earning" else "deductions"
if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1:
for leave_encashment in leave_encashments:
frappe.db.sql(
"""
UPDATE `tabSalary Detail`
SET additional_salary = %s
WHERE parenttype = 'Salary Slip'
and parentfield = %s
and parent = %s
and salary_component = %s
""" UPDATE `tabAdditional Salary`
SET ref_doctype = 'Leave Encashment', ref_docname = %s
WHERE name = %s
""",
(salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]),
(leave_encashment["name"], leave_encashment["additional_salary"]),
)
if frappe.db.has_column("Employee Incentive", "additional_salary"):
employee_incentives = frappe.get_all(
"Employee Incentive",
fields=["name", "additional_salary"],
filters={"additional_salary": ["!=", ""]},
)
for incentive in employee_incentives:
frappe.db.sql(
""" UPDATE `tabAdditional Salary`
SET ref_doctype = 'Employee Incentive', ref_docname = %s
WHERE name = %s
""",
(incentive["name"], incentive["additional_salary"]),
)
if frappe.db.has_column("Additional Salary", "salary_slip"):
additional_salaries = frappe.get_all(
"Additional Salary",
fields=["name", "salary_slip", "type", "salary_component"],
filters={"salary_slip": ["!=", ""]},
group_by="salary_slip",
)
salary_slips = [sal["salary_slip"] for sal in additional_salaries]
for salary in additional_salaries:
comp_type = "earnings" if salary["type"] == "Earning" else "deductions"
if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1:
frappe.db.sql(
"""
UPDATE `tabSalary Detail`
SET additional_salary = %s
WHERE parenttype = 'Salary Slip'
and parentfield = %s
and parent = %s
and salary_component = %s
""",
(salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]),
)

View File

@@ -376,13 +376,19 @@ class SalarySlip(TransactionBase):
if joining_date and (getdate(self.start_date) < joining_date <= getdate(self.end_date)):
start_date = joining_date
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
unmarked_days, include_holidays_in_total_working_days, self.start_date, joining_date
unmarked_days,
include_holidays_in_total_working_days,
self.start_date,
add_days(joining_date, -1),
)
if relieving_date and (getdate(self.start_date) <= relieving_date < getdate(self.end_date)):
end_date = relieving_date
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
unmarked_days, include_holidays_in_total_working_days, relieving_date, self.end_date
unmarked_days,
include_holidays_in_total_working_days,
add_days(relieving_date, 1),
self.end_date,
)
# exclude days for which attendance has been marked
@@ -408,10 +414,10 @@ class SalarySlip(TransactionBase):
from erpnext.hr.doctype.employee.employee import is_holiday
if include_holidays_in_total_working_days:
unmarked_days -= date_diff(end_date, start_date)
unmarked_days -= date_diff(end_date, start_date) + 1
else:
# exclude only if not holidays
for days in range(date_diff(end_date, start_date)):
for days in range(date_diff(end_date, start_date) + 1):
date = add_days(end_date, -days)
if not is_holiday(self.employee, date):
unmarked_days -= 1

View File

@@ -128,6 +128,72 @@ class TestSalarySlip(unittest.TestCase):
},
)
def test_payment_days_for_mid_joinee_including_holidays(self):
no_of_days = self.get_no_of_days()
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
for days in range(date_diff(month_end_date, month_start_date) + 1):
date = add_days(month_start_date, days)
mark_attendance(new_emp_id, date, "Present", ignore_validate=True)
# Case 1: relieving in mid month
frappe.db.set_value(
"Employee",
new_emp_id,
{"date_of_joining": month_start_date, "relieving_date": relieving_date, "status": "Active"},
)
new_ss = make_employee_salary_slip(
"test_payment_days_based_on_joining_date@salary.com",
"Monthly",
"Test Payment Based On Attendence",
)
self.assertEqual(new_ss.payment_days, no_of_days[0] - 5)
# Case 2: joining in mid month
frappe.db.set_value(
"Employee",
new_emp_id,
{"date_of_joining": joining_date, "relieving_date": month_end_date, "status": "Active"},
)
frappe.delete_doc("Salary Slip", new_ss.name, force=True)
new_ss = make_employee_salary_slip(
"test_payment_days_based_on_joining_date@salary.com",
"Monthly",
"Test Payment Based On Attendence",
)
self.assertEqual(new_ss.payment_days, no_of_days[0] - 3)
# Case 3: joining and relieving in mid-month
frappe.db.set_value(
"Employee",
new_emp_id,
{"date_of_joining": joining_date, "relieving_date": relieving_date, "status": "Left"},
)
frappe.delete_doc("Salary Slip", new_ss.name, force=True)
new_ss = make_employee_salary_slip(
"test_payment_days_based_on_joining_date@salary.com",
"Monthly",
"Test Payment Based On Attendence",
)
self.assertEqual(new_ss.total_working_days, no_of_days[0])
self.assertEqual(new_ss.payment_days, no_of_days[0] - 8)
@change_settings(
"Payroll Settings",
{
"payroll_based_on": "Attendance",
"consider_unmarked_attendance_as": "Absent",
"include_holidays_in_total_working_days": True,
},
)
def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):
# tests mid month joining and relieving along with unmarked days
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
no_of_days = self.get_no_of_days()
@@ -135,12 +201,6 @@ class TestSalarySlip(unittest.TestCase):
new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
frappe.db.set_value(
"Employee",
new_emp_id,
{"date_of_joining": joining_date, "relieving_date": relieving_date, "status": "Left"},
)
holidays = 0
for days in range(date_diff(relieving_date, joining_date) + 1):
@@ -150,6 +210,12 @@ class TestSalarySlip(unittest.TestCase):
else:
holidays += 1
frappe.db.set_value(
"Employee",
new_emp_id,
{"date_of_joining": joining_date, "relieving_date": relieving_date, "status": "Left"},
)
new_ss = make_employee_salary_slip(
"test_payment_days_based_on_joining_date@salary.com",
"Monthly",

View File

@@ -84,6 +84,7 @@ class Project(Document):
type=task_details.type,
issue=task_details.issue,
is_group=task_details.is_group,
color=task_details.color,
)
).insert()

View File

@@ -1500,6 +1500,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return;
}
// Target doc created from a mapped doc
if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
return;
}
return this.frm.call({
method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule",
args: { args: args, doc: me.frm.doc },
@@ -1616,7 +1621,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
}
if (d.free_item_data) {
if (d.free_item_data.length > 0) {
me.apply_product_discount(d);
}

View File

@@ -12,6 +12,7 @@ import traceback
import frappe
import jwt
import requests
import six
from frappe import _, bold
from frappe.core.page.background_jobs.background_jobs import get_info
@@ -57,6 +58,7 @@ def validate_eligibility(doc):
invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")})
invalid_supply_type = doc.get("gst_category") not in [
"Registered Regular",
"Registered Composition",
"SEZ",
"Overseas",
"Deemed Export",
@@ -124,24 +126,33 @@ def read_json(name):
def get_transaction_details(invoice):
supply_type = ""
if invoice.gst_category == "Registered Regular":
if (
invoice.gst_category == "Registered Regular" or invoice.gst_category == "Registered Composition"
):
supply_type = "B2B"
elif invoice.gst_category == "SEZ":
supply_type = "SEZWOP"
if invoice.export_type == "Without Payment of Tax":
supply_type = "SEZWOP"
else:
supply_type = "SEZWP"
elif invoice.gst_category == "Overseas":
supply_type = "EXPWOP"
if invoice.export_type == "Without Payment of Tax":
supply_type = "EXPWOP"
else:
supply_type = "EXPWP"
elif invoice.gst_category == "Deemed Export":
supply_type = "DEXP"
if not supply_type:
rr, sez, overseas, export = (
rr, rc, sez, overseas, export = (
bold("Registered Regular"),
bold("Registered Composition"),
bold("SEZ"),
bold("Overseas"),
bold("Deemed Export"),
)
frappe.throw(
_("GST category should be one of {}, {}, {}, {}").format(rr, sez, overseas, export),
_("GST category should be one of {}, {}, {}, {}, {}").format(rr, rc, sez, overseas, export),
title=_("Invalid Supply Type"),
)
@@ -315,10 +326,14 @@ def update_item_taxes(invoice, item):
item.cess_rate += item_tax_rate
item.cess_amount += abs(item_tax_amount_after_discount)
for tax_type in ["igst", "cgst", "sgst"]:
for tax_type in ["igst", "cgst", "sgst", "utgst"]:
if t.account_head in gst_accounts[f"{tax_type}_account"]:
item.tax_rate += item_tax_rate
item[f"{tax_type}_amount"] += abs(item_tax_amount)
if tax_type == "utgst":
# utgst taxes are reported same as sgst tax
item["sgst_amount"] += abs(item_tax_amount)
else:
item[f"{tax_type}_amount"] += abs(item_tax_amount)
else:
# TODO: other charges per item
pass
@@ -360,11 +375,15 @@ def update_invoice_taxes(invoice, invoice_value_details):
# using after discount amt since item also uses after discount amt for cess calc
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
for tax_type in ["igst", "cgst", "sgst"]:
for tax_type in ["igst", "cgst", "sgst", "utgst"]:
if t.account_head in gst_accounts[f"{tax_type}_account"]:
if tax_type == "utgst":
invoice_value_details["total_sgst_amt"] += abs(tax_amount)
else:
invoice_value_details[f"total_{tax_type}_amt"] += abs(tax_amount)
invoice_value_details[f"total_{tax_type}_amt"] += abs(tax_amount)
update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
else:
invoice_value_details.total_other_charges += abs(tax_amount)
@@ -544,6 +563,7 @@ def validate_totals(einvoice):
+ flt(value_details["CgstVal"])
+ flt(value_details["SgstVal"])
+ flt(value_details["IgstVal"])
+ flt(value_details["CesVal"])
+ flt(value_details["OthChrg"])
- flt(value_details["Discount"])
)
@@ -820,14 +840,25 @@ class GSPConnector:
return self.e_invoice_settings.auth_token
def make_request(self, request_type, url, headers=None, data=None):
if request_type == "post":
res = make_post_request(url, headers=headers, data=data)
else:
res = make_get_request(url, headers=headers, data=data)
try:
if request_type == "post":
res = make_post_request(url, headers=headers, data=data)
else:
res = make_get_request(url, headers=headers, data=data)
except requests.exceptions.HTTPError as e:
if e.response.status_code in [401, 403] and not hasattr(self, "token_auto_refreshed"):
self.auto_refresh_token()
headers = self.get_headers()
return self.make_request(request_type, url, headers, data)
self.log_request(url, headers, data, res)
return res
def auto_refresh_token(self):
self.fetch_auth_token()
self.token_auto_refreshed = True
def log_request(self, url, headers, data, res):
headers.update({"password": self.credentials.password})
request_log = frappe.get_doc(
@@ -1065,7 +1096,7 @@ class GSPConnector:
"Distance": cint(eway_bill_details.distance),
"TransMode": eway_bill_details.mode_of_transport,
"TransId": eway_bill_details.gstin,
"TransName": eway_bill_details.transporter,
"TransName": eway_bill_details.name,
"TrnDocDt": eway_bill_details.document_date,
"TrnDocNo": eway_bill_details.document_name,
"VehNo": eway_bill_details.vehicle_no,

View File

@@ -714,7 +714,7 @@ def get_custom_fields():
insert_after="customer",
no_copy=1,
print_hide=1,
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0',
depends_on='eval:in_list(["Registered Regular", "Registered Composition", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0',
),
dict(
fieldname="irn_cancelled",

View File

@@ -341,7 +341,7 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code):
tax_categories = frappe.get_all(
"Tax Category",
fields=["name", "is_inter_state", "gst_state"],
filters={"is_inter_state": is_inter_state, "is_reverse_charge": 0},
filters={"is_inter_state": is_inter_state, "is_reverse_charge": 0, "disabled": 0},
)
default_tax = ""
@@ -825,7 +825,7 @@ def get_gst_accounts(
gst_settings_accounts = frappe.get_all(
"GST Account",
filters=filters,
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"],
fields=["cgst_account", "sgst_account", "igst_account", "cess_account", "utgst_account"],
)
if not gst_settings_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:

View File

@@ -78,8 +78,9 @@ frappe.query_reports["GSTR-1"] = {
}
});
report.page.add_inner_button(__("Download as JSON"), function () {
let filters = report.get_values();
frappe.call({
method: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
args: {

View File

@@ -95,10 +95,9 @@ class VATAuditReport(object):
as_dict=1,
)
for d in items:
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {"net_amount": 0.0})
self.invoice_items[d.parent][d.item_code]["net_amount"] += d.get("base_net_amount", 0)
self.invoice_items[d.parent][d.item_code]["is_zero_rated"] = d.is_zero_rated
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {"net_amount": 0.0})
self.invoice_items[d.parent][d.item_code]["net_amount"] += d.get("base_net_amount", 0)
self.invoice_items[d.parent][d.item_code]["is_zero_rated"] = d.is_zero_rated
def get_items_based_on_tax_rate(self, doctype):
self.items_based_on_tax_rate = frappe._dict()
@@ -110,7 +109,7 @@ class VATAuditReport(object):
self.tax_details = frappe.db.sql(
"""
SELECT
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
parent, account_head, item_wise_tax_detail
FROM
`tab%s`
WHERE
@@ -123,7 +122,7 @@ class VATAuditReport(object):
tuple([doctype] + list(self.invoices.keys())),
)
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
for parent, account, item_wise_tax_detail in self.tax_details:
if item_wise_tax_detail:
try:
if account in self.sa_vat_accounts:
@@ -135,7 +134,7 @@ class VATAuditReport(object):
# to skip items with non-zero tax rate in multiple rows
if taxes[0] == 0 and not is_zero_rated:
continue
tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes)
tax_rate = self.get_item_amount_map(parent, item_code, taxes)
if tax_rate is not None:
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}).setdefault(
@@ -151,16 +150,22 @@ class VATAuditReport(object):
tax_rate = taxes[0]
tax_amount = taxes[1]
gross_amount = net_amount + tax_amount
item_amount_map = self.item_tax_rate.setdefault(parent, {}).setdefault(item_code, [])
amount_dict = {
"tax_rate": tax_rate,
"gross_amount": gross_amount,
"tax_amount": tax_amount,
"net_amount": net_amount,
}
item_amount_map.append(amount_dict)
return tax_rate, item_amount_map
self.item_tax_rate.setdefault(parent, {}).setdefault(
item_code,
{
"tax_rate": tax_rate,
"gross_amount": 0.0,
"tax_amount": 0.0,
"net_amount": 0.0,
},
)
self.item_tax_rate[parent][item_code]["net_amount"] += net_amount
self.item_tax_rate[parent][item_code]["tax_amount"] += tax_amount
self.item_tax_rate[parent][item_code]["gross_amount"] += gross_amount
return tax_rate
def get_conditions(self):
conditions = ""
@@ -205,9 +210,10 @@ class VATAuditReport(object):
for inv, inv_data in self.invoices.items():
if self.items_based_on_tax_rate.get(inv):
for rate, items in self.items_based_on_tax_rate.get(inv).items():
row = {"tax_amount": 0.0, "gross_amount": 0.0, "net_amount": 0.0}
consolidated_data_map.setdefault(rate, {"data": []})
for item in items:
row = {}
item_details = self.item_tax_rate.get(inv).get(item)
row["account"] = inv_data.get("account")
row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
@@ -216,10 +222,11 @@ class VATAuditReport(object):
row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier"
row["party"] = inv_data.get("party")
row["remarks"] = inv_data.get("remarks")
row["gross_amount"] = item_details[0].get("gross_amount")
row["tax_amount"] = item_details[0].get("tax_amount")
row["net_amount"] = item_details[0].get("net_amount")
consolidated_data_map[rate]["data"].append(row)
row["gross_amount"] += item_details.get("gross_amount")
row["tax_amount"] += item_details.get("tax_amount")
row["net_amount"] += item_details.get("net_amount")
consolidated_data_map[rate]["data"].append(row)
return consolidated_data_map

View File

@@ -245,7 +245,7 @@ def make_custom_fields():
"Supplier Quotation Item": invoice_item_fields,
}
create_custom_fields(custom_fields)
create_custom_fields(custom_fields, ignore_validate=True)
def add_print_formats():

View File

@@ -100,7 +100,8 @@ class Customer(TransactionBase):
@frappe.whitelist()
def get_customer_group_details(self):
doc = frappe.get_doc("Customer Group", self.customer_group)
self.accounts = self.credit_limits = []
self.accounts = []
self.credit_limits = []
self.payment_terms = self.default_price_list = ""
tables = [["accounts", "account"], ["credit_limits", "credit_limit"]]

View File

@@ -47,7 +47,8 @@ class TestCustomer(FrappeTestCase):
c_doc.customer_name = "Testing Customer"
c_doc.customer_group = "_Testing Customer Group"
c_doc.payment_terms = c_doc.default_price_list = ""
c_doc.accounts = c_doc.credit_limits = []
c_doc.accounts = []
c_doc.credit_limits = []
c_doc.insert()
c_doc.get_customer_group_details()
self.assertEqual(c_doc.payment_terms, "_Test Payment Term Template 3")

View File

@@ -26,6 +26,7 @@ class Quotation(SellingController):
self.set_status()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_valid_till()
self.validate_shopping_cart_items()
self.set_customer_name()
if self.items:
self.with_items = 1
@@ -38,6 +39,26 @@ class Quotation(SellingController):
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
frappe.throw(_("Valid till date cannot be before transaction date"))
def validate_shopping_cart_items(self):
if self.order_type != "Shopping Cart":
return
for item in self.items:
has_web_item = frappe.db.exists("Website Item", {"item_code": item.item_code})
# If variant is unpublished but template is published: valid
template = frappe.get_cached_value("Item", item.item_code, "variant_of")
if template and not has_web_item:
has_web_item = frappe.db.exists("Website Item", {"item_code": template})
if not has_web_item:
frappe.throw(
_("Row #{0}: Item {1} must have a Website Item for Shopping Cart Quotations").format(
item.idx, frappe.bold(item.item_code)
),
title=_("Unpublished Item"),
)
def has_sales_order(self):
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})

View File

@@ -130,6 +130,15 @@ class TestQuotation(FrappeTestCase):
quotation.submit()
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
def test_shopping_cart_without_website_item(self):
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
frappe.get_last_doc("Website Item", {"item_code": "_Test Item Home Desktop 100"}).delete()
quotation = frappe.copy_doc(test_records[0])
quotation.order_type = "Shopping Cart"
quotation.valid_till = getdate()
self.assertRaises(frappe.ValidationError, quotation.validate)
def test_create_quotation_with_margin(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
from erpnext.selling.doctype.sales_order.sales_order import (

View File

@@ -152,7 +152,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
}
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
if (flt(doc.per_picked, 6) < 100 && flt(doc.per_delivered, 6) < 100) {
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
}
const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
const order_is_maintenance = ["Maintenance"].indexOf(doc.order_type) !== -1;

View File

@@ -25,6 +25,10 @@
"po_no",
"po_date",
"tax_id",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"contact_info",
"customer_address",
"address_display",
@@ -113,7 +117,6 @@
"is_internal_customer",
"represents_company",
"inter_company_order_reference",
"project",
"party_account_currency",
"column_break_77",
"source",
@@ -1520,14 +1523,31 @@
"fieldname": "per_picked",
"fieldtype": "Percent",
"label": "% Picked",
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-03-15 21:38:31.437586",
"modified": "2022-04-26 14:38:18.350207",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1606,4 +1626,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -455,6 +455,16 @@ class SalesOrder(SellingController):
if tot_qty != 0:
self.db_set("per_delivered", flt(delivered_qty / tot_qty) * 100, update_modified=False)
def update_picking_status(self):
total_picked_qty = 0.0
total_qty = 0.0
for so_item in self.items:
total_picked_qty += flt(so_item.picked_qty)
total_qty += flt(so_item.stock_qty)
per_picked = total_picked_qty / total_qty * 100
self.db_set("per_picked", flt(per_picked), update_modified=False)
def set_indicator(self):
"""Set indicator for portal"""
if self.per_billed < 100 and self.per_delivered < 100:
@@ -1302,9 +1312,30 @@ def make_inter_company_purchase_order(source_name, target_doc=None):
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
def update_item_quantity(source, target, source_parent):
target.qty = flt(source.qty) - flt(source.delivered_qty)
target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
from erpnext.stock.doctype.packed_item.packed_item import is_product_bundle
def update_item_quantity(source, target, source_parent) -> None:
picked_qty = flt(source.picked_qty) / (flt(source.conversion_factor) or 1)
qty_to_be_picked = flt(source.qty) - max(picked_qty, flt(source.delivered_qty))
target.qty = qty_to_be_picked
target.stock_qty = qty_to_be_picked * flt(source.conversion_factor)
def update_packed_item_qty(source, target, source_parent) -> None:
qty = flt(source.qty)
for item in source_parent.items:
if source.parent_detail_docname == item.name:
picked_qty = flt(item.picked_qty) / (flt(item.conversion_factor) or 1)
pending_percent = (item.qty - max(picked_qty, item.delivered_qty)) / item.qty
target.qty = target.stock_qty = qty * pending_percent
return
def should_pick_order_item(item) -> bool:
return (
abs(item.delivered_qty) < abs(item.qty)
and item.delivered_by_supplier != 1
and not is_product_bundle(item.item_code)
)
doc = get_mapped_doc(
"Sales Order",
@@ -1315,8 +1346,17 @@ def create_pick_list(source_name, target_doc=None):
"doctype": "Pick List Item",
"field_map": {"parent": "sales_order", "name": "sales_order_item"},
"postprocess": update_item_quantity,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
and doc.delivered_by_supplier != 1,
"condition": should_pick_order_item,
},
"Packed Item": {
"doctype": "Pick List Item",
"field_map": {
"parent": "sales_order",
"name": "sales_order_item",
"parent_detail_docname": "product_bundle_item",
},
"field_no_map": ["picked_qty"],
"postprocess": update_packed_item_qty,
},
},
target_doc,

View File

@@ -801,13 +801,15 @@
{
"fieldname": "picked_qty",
"fieldtype": "Float",
"label": "Picked Qty"
"label": "Picked Qty (in Stock UOM)",
"no_copy": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-03-15 20:17:33.984799",
"modified": "2022-04-27 03:15:34.366563",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@@ -343,9 +343,9 @@ erpnext.PointOfSale.Controller = class {
toggle_other_sections: (show) => {
if (show) {
this.item_details.$component.is(':visible') ? this.item_details.$component.css('display', 'none') : '';
this.item_selector.$component.css('display', 'none');
this.item_selector.toggle_component(false);
} else {
this.item_selector.$component.css('display', 'flex');
this.item_selector.toggle_component(true);
}
},

View File

@@ -130,10 +130,10 @@ erpnext.PointOfSale.ItemCart = class {
},
cols: 5,
keys: [
[ 1, 2, 3, __('Quantity') ],
[ 4, 5, 6, __('Discount') ],
[ 7, 8, 9, __('Rate') ],
[ '.', 0, __('Delete'), __('Remove') ]
[ 1, 2, 3, 'Quantity' ],
[ 4, 5, 6, 'Discount' ],
[ 7, 8, 9, 'Rate' ],
[ '.', 0, 'Delete', 'Remove' ]
],
css_classes: [
[ '', '', '', 'col-span-2' ],

View File

@@ -179,6 +179,25 @@ erpnext.PointOfSale.ItemSelector = class {
});
this.search_field.toggle_label(false);
this.item_group_field.toggle_label(false);
this.attach_clear_btn();
}
attach_clear_btn() {
this.search_field.$wrapper.find('.control-input').append(
`<span class="link-btn" style="top: 2px;">
<a class="btn-open no-decoration" title="${__("Clear")}">
${frappe.utils.icon('close', 'sm')}
</a>
</span>`
);
this.$clear_search_btn = this.search_field.$wrapper.find('.link-btn');
this.$clear_search_btn.on('click', 'a', () => {
this.set_search_value('');
this.search_field.set_focus();
});
}
set_search_value(value) {
@@ -252,6 +271,16 @@ erpnext.PointOfSale.ItemSelector = class {
const search_term = e.target.value;
this.filter_items({ search_term });
}, 300);
this.$clear_search_btn.toggle(
Boolean(this.search_field.$input.val())
);
});
this.search_field.$input.on('focus', () => {
this.$clear_search_btn.toggle(
Boolean(this.search_field.$input.val())
);
});
}
@@ -284,7 +313,7 @@ erpnext.PointOfSale.ItemSelector = class {
if (this.items.length == 1) {
this.$items_container.find(".item-wrapper").click();
frappe.utils.play_sound("submit");
$(this.search_field.$input[0]).val("").trigger("input");
this.set_search_value('');
} else if (this.items.length == 0 && this.barcode_scanned) {
// only show alert of barcode is scanned and enter is pressed
frappe.show_alert({
@@ -293,7 +322,7 @@ erpnext.PointOfSale.ItemSelector = class {
});
frappe.utils.play_sound("error");
this.barcode_scanned = false;
$(this.search_field.$input[0]).val("").trigger("input");
this.set_search_value('');
}
});
}
@@ -350,6 +379,7 @@ erpnext.PointOfSale.ItemSelector = class {
}
toggle_component(show) {
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
this.set_search_value('');
this.$component.css('display', show ? 'flex': 'none');
}
};

View File

@@ -25,7 +25,7 @@ erpnext.PointOfSale.NumberPad = class {
const fieldname = fieldnames && fieldnames[number] ?
fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number;
return a2 + `<div class="numpad-btn ${class_to_append}" data-button-value="${fieldname}">${number}</div>`;
return a2 + `<div class="numpad-btn ${class_to_append}" data-button-value="${fieldname}">${__(number)}</div>`;
}, '');
}, '');
}

View File

@@ -27,28 +27,55 @@ function get_filters() {
"default": frappe.datetime.get_today()
},
{
"fieldname":"sales_order",
"label": __("Sales Order"),
"fieldtype": "MultiSelectList",
"fieldname":"customer_group",
"label": __("Customer Group"),
"fieldtype": "Link",
"width": 100,
"options": "Sales Order",
"get_data": function(txt) {
return frappe.db.get_link_options("Sales Order", txt, this.filters());
},
"filters": () => {
return {
docstatus: 1,
payment_terms_template: ['not in', ['']],
company: frappe.query_report.get_filter_value("company"),
transaction_date: ['between', [frappe.query_report.get_filter_value("period_start_date"), frappe.query_report.get_filter_value("period_end_date")]]
"options": "Customer Group",
},
{
"fieldname":"customer",
"label": __("Customer"),
"fieldtype": "Link",
"width": 100,
"options": "Customer",
"get_query": () => {
var customer_group = frappe.query_report.get_filter_value('customer_group');
return{
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
"filters": [
['Customer', 'disabled', '=', '0'],
['Customer Group','name', '=', customer_group]
]
}
}
},
{
"fieldname":"item_group",
"label": __("Item Group"),
"fieldtype": "Link",
"width": 100,
"options": "Item Group",
},
{
"fieldname":"item",
"label": __("Item"),
"fieldtype": "Link",
"width": 100,
"options": "Item",
"get_query": () => {
var item_group = frappe.query_report.get_filter_value('item_group');
return{
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
"filters": [
['Item', 'disabled', '=', '0'],
['Item Group','name', '=', item_group]
]
}
},
on_change: function(){
frappe.query_report.refresh();
}
}
]
return filters;
}

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _, qb, query_builder
from frappe.query_builder import functions
from frappe.query_builder import Criterion, functions
def get_columns():
@@ -14,6 +14,12 @@ def get_columns():
"fieldtype": "Link",
"options": "Sales Order",
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
},
{
"label": _("Posting Date"),
"fieldname": "submitted",
@@ -67,6 +73,55 @@ def get_columns():
return columns
def get_descendants_of(doctype, group_name):
group_doc = qb.DocType(doctype)
# get lft and rgt of group node
lft, rgt = (
qb.from_(group_doc).select(group_doc.lft, group_doc.rgt).where(group_doc.name == group_name)
).run()[0]
# get all children of group node
query = (
qb.from_(group_doc).select(group_doc.name).where((group_doc.lft >= lft) & (group_doc.rgt <= rgt))
)
child_nodes = []
for x in query.run():
child_nodes.append(x[0])
return child_nodes
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customers_or_items(doctype, txt, searchfield, start, page_len, filters):
filter_list = []
if isinstance(filters, list):
for item in filters:
if item[0] == doctype:
filter_list.append(item)
elif item[0] == "Customer Group":
if item[3] != "":
filter_list.append(
[doctype, "customer_group", "in", get_descendants_of("Customer Group", item[3])]
)
elif item[0] == "Item Group":
if item[3] != "":
filter_list.append([doctype, "item_group", "in", get_descendants_of("Item Group", item[3])])
if searchfield and txt:
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute(
doctype,
filters=filter_list,
fields=["name", "customer_group"] if doctype == "Customer" else ["name", "item_group"],
limit_start=start,
limit_page_length=page_len,
as_list=True,
)
def get_conditions(filters):
"""
Convert filter options to conditions used in query
@@ -79,11 +134,37 @@ def get_conditions(filters):
conditions.start_date = filters.period_start_date or frappe.utils.add_months(
conditions.end_date, -1
)
conditions.sales_order = filters.sales_order or []
return conditions
def build_filter_criterions(filters):
filters = frappe._dict(filters) if filters else frappe._dict({})
qb_criterions = []
if filters.customer_group:
qb_criterions.append(
qb.DocType("Sales Order").customer_group.isin(
get_descendants_of("Customer Group", filters.customer_group)
)
)
if filters.customer:
qb_criterions.append(qb.DocType("Sales Order").customer == filters.customer)
if filters.item_group:
qb_criterions.append(
qb.DocType("Sales Order Item").item_group.isin(
get_descendants_of("Item Group", filters.item_group)
)
)
if filters.item:
qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
return qb_criterions
def get_so_with_invoices(filters):
"""
Get Sales Order with payment terms template with their associated Invoices
@@ -92,16 +173,23 @@ def get_so_with_invoices(filters):
so = qb.DocType("Sales Order")
ps = qb.DocType("Payment Schedule")
soi = qb.DocType("Sales Order Item")
conditions = get_conditions(filters)
filter_criterions = build_filter_criterions(filters)
datediff = query_builder.CustomFunction("DATEDIFF", ["cur_date", "due_date"])
ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
conditions = get_conditions(filters)
query_so = (
qb.from_(so)
.join(soi)
.on(soi.parent == so.name)
.join(ps)
.on(ps.parent == so.name)
.select(
so.name,
so.customer,
so.transaction_date.as_("submitted"),
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
ps.payment_term,
@@ -117,12 +205,10 @@ def get_so_with_invoices(filters):
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
)
.where(Criterion.all(filter_criterions))
.orderby(so.name, so.transaction_date, ps.due_date)
)
if conditions.sales_order != []:
query_so = query_so.where(so.name.isin(conditions.sales_order))
sorders = query_so.run(as_dict=True)
invoices = []

View File

@@ -11,10 +11,13 @@ from erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_s
)
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template"]
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template", "Customer"]
class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_payment_terms_template(self):
# create template for 50-50 payments
template = None
@@ -48,9 +51,9 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
template.insert()
self.template = template
def test_payment_terms_status(self):
def test_01_payment_terms_status(self):
self.create_payment_terms_template()
item = create_item(item_code="_Test Excavator", is_stock_item=0)
item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
so = make_sales_order(
transaction_date="2021-06-15",
delivery_date=add_days("2021-06-15", -30),
@@ -78,13 +81,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
"sales_order": [so.name],
"item": item.item_code,
}
)
expected_value = [
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -98,6 +102,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -132,11 +137,11 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
)
doc.insert()
def test_alternate_currency(self):
def test_02_alternate_currency(self):
transaction_date = "2021-06-15"
self.create_payment_terms_template()
self.create_exchange_rate(transaction_date)
item = create_item(item_code="_Test Excavator", is_stock_item=0)
item = create_item(item_code="_Test Excavator 2", is_stock_item=0)
so = make_sales_order(
transaction_date=transaction_date,
currency="USD",
@@ -166,7 +171,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
"sales_order": [so.name],
"item": item.item_code,
}
)
@@ -174,6 +179,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
expected_value = [
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -187,6 +193,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -200,3 +207,134 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
]
self.assertEqual(data, expected_value)
def test_03_group_filters(self):
transaction_date = "2021-06-15"
self.create_payment_terms_template()
item1 = create_item(item_code="_Test Excavator 1", is_stock_item=0)
item1.item_group = "Products"
item1.save()
so1 = make_sales_order(
transaction_date=transaction_date,
delivery_date=add_days(transaction_date, -30),
item=item1.item_code,
qty=1,
rate=1000000,
do_not_save=True,
)
so1.po_no = ""
so1.taxes_and_charges = ""
so1.taxes = ""
so1.payment_terms_template = self.template.name
so1.save()
so1.submit()
item2 = create_item(item_code="_Test Steel", is_stock_item=0)
item2.item_group = "Raw Material"
item2.save()
so2 = make_sales_order(
customer="_Test Customer 1",
transaction_date=transaction_date,
delivery_date=add_days(transaction_date, -30),
item=item2.item_code,
qty=100,
rate=1000,
do_not_save=True,
)
so2.po_no = ""
so2.taxes_and_charges = ""
so2.taxes = ""
so2.payment_terms_template = self.template.name
so2.save()
so2.submit()
base_filters = {
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
}
expected_value_so1 = [
{
"name": so1.name,
"customer": so1.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 6, 30),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 500000.0,
"paid_amount": 0.0,
"invoices": "",
},
{
"name": so1.name,
"customer": so1.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 7, 15),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 500000.0,
"paid_amount": 0.0,
"invoices": "",
},
]
expected_value_so2 = [
{
"name": so2.name,
"customer": so2.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 6, 30),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 50000.0,
"paid_amount": 0.0,
"invoices": "",
},
{
"name": so2.name,
"customer": so2.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 7, 15),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 50000.0,
"paid_amount": 0.0,
"invoices": "",
},
]
group_filters = [
{"customer_group": "All Customer Groups"},
{"item_group": "All Item Groups"},
{"item_group": "Products"},
{"item_group": "Raw Material"},
]
expected_values_for_group_filters = [
expected_value_so1 + expected_value_so2,
expected_value_so1 + expected_value_so2,
expected_value_so1,
expected_value_so2,
]
for idx, g in enumerate(group_filters, 0):
# build filter
filters = frappe._dict({}).update(base_filters).update(g)
with self.subTest(filters=filters):
columns, data, message, chart = execute(filters)
self.assertEqual(data, expected_values_for_group_filters[idx])

View File

@@ -54,6 +54,7 @@ def get_conditions(filters):
def get_data(conditions, filters):
# nosemgrep
data = frappe.db.sql(
"""
SELECT
@@ -65,6 +66,7 @@ def get_data(conditions, filters):
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
soi.qty, soi.delivered_qty,
(soi.qty - soi.delivered_qty) AS pending_qty,
IF((SELECT pending_qty) = 0, (TO_SECONDS(Max(dn.posting_date))-TO_SECONDS(so.transaction_date)), 0) as time_taken_to_deliver,
IFNULL(SUM(sii.qty), 0) as billed_qty,
soi.base_amount as amount,
(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
@@ -75,9 +77,13 @@ def get_data(conditions, filters):
soi.description as description
FROM
`tabSales Order` so,
`tabSales Order Item` soi
(`tabSales Order Item` soi
LEFT JOIN `tabSales Invoice Item` sii
ON sii.so_detail = soi.name and sii.docstatus = 1
ON sii.so_detail = soi.name and sii.docstatus = 1)
LEFT JOIN `tabDelivery Note Item` dni
on dni.so_detail = soi.name
LEFT JOIN `tabDelivery Note` dn
on dni.parent = dn.name and dn.docstatus = 1
WHERE
soi.parent = so.name
and so.status not in ('Stopped', 'Closed', 'On Hold')
@@ -264,6 +270,12 @@ def get_columns(filters):
},
{"label": _("Delivery Date"), "fieldname": "delivery_date", "fieldtype": "Date", "width": 120},
{"label": _("Delay (in Days)"), "fieldname": "delay", "fieldtype": "Data", "width": 100},
{
"label": _("Time Taken to Deliver"),
"fieldname": "time_taken_to_deliver",
"fieldtype": "Duration",
"width": 100,
},
]
)
if not filters.get("group_by_so"):

View File

@@ -0,0 +1,166 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.selling.report.sales_order_analysis.sales_order_analysis import execute
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Delivery Note"]
class TestSalesOrderAnalysis(FrappeTestCase):
def create_sales_order(self, transaction_date):
item = create_item(item_code="_Test Excavator", is_stock_item=0)
so = make_sales_order(
transaction_date=transaction_date,
item=item.item_code,
qty=10,
rate=100000,
do_not_save=True,
)
so.po_no = ""
so.taxes_and_charges = ""
so.taxes = ""
so.items[0].delivery_date = add_days(transaction_date, 15)
so.save()
so.submit()
return item, so
def create_sales_invoice(self, so):
sinv = make_sales_invoice(so.name)
sinv.posting_date = so.transaction_date
sinv.taxes_and_charges = ""
sinv.taxes = ""
sinv.insert()
sinv.submit()
return sinv
def create_delivery_note(self, so):
dn = make_delivery_note(so.name)
dn.set_posting_time = True
dn.posting_date = add_days(so.transaction_date, 1)
dn.save()
dn.submit()
return dn
def test_01_so_to_deliver_and_bill(self):
transaction_date = "2021-06-01"
item, so = self.create_sales_order(transaction_date)
columns, data, message, chart = execute(
{
"company": "_Test Company",
"from_date": "2021-06-01",
"to_date": "2021-06-30",
"status": ["To Deliver and Bill"],
}
)
expected_value = {
"status": "To Deliver and Bill",
"sales_order": so.name,
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
"qty": 10,
"delivered_qty": 0,
"pending_qty": 10,
"qty_to_bill": 10,
"time_taken_to_deliver": 0,
}
self.assertEqual(len(data), 1)
for key, val in expected_value.items():
with self.subTest(key=key, val=val):
self.assertEqual(data[0][key], val)
def test_02_so_to_deliver(self):
transaction_date = "2021-06-01"
item, so = self.create_sales_order(transaction_date)
self.create_sales_invoice(so)
columns, data, message, chart = execute(
{
"company": "_Test Company",
"from_date": "2021-06-01",
"to_date": "2021-06-30",
"status": ["To Deliver"],
}
)
expected_value = {
"status": "To Deliver",
"sales_order": so.name,
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
"qty": 10,
"delivered_qty": 0,
"pending_qty": 10,
"qty_to_bill": 0,
"time_taken_to_deliver": 0,
}
self.assertEqual(len(data), 1)
for key, val in expected_value.items():
with self.subTest(key=key, val=val):
self.assertEqual(data[0][key], val)
def test_03_so_to_bill(self):
transaction_date = "2021-06-01"
item, so = self.create_sales_order(transaction_date)
self.create_delivery_note(so)
columns, data, message, chart = execute(
{
"company": "_Test Company",
"from_date": "2021-06-01",
"to_date": "2021-06-30",
"status": ["To Bill"],
}
)
expected_value = {
"status": "To Bill",
"sales_order": so.name,
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
"qty": 10,
"delivered_qty": 10,
"pending_qty": 0,
"qty_to_bill": 10,
"time_taken_to_deliver": 86400,
}
self.assertEqual(len(data), 1)
for key, val in expected_value.items():
with self.subTest(key=key, val=val):
self.assertEqual(data[0][key], val)
def test_04_so_completed(self):
transaction_date = "2021-06-01"
item, so = self.create_sales_order(transaction_date)
self.create_sales_invoice(so)
self.create_delivery_note(so)
columns, data, message, chart = execute(
{
"company": "_Test Company",
"from_date": "2021-06-01",
"to_date": "2021-06-30",
"status": ["Completed"],
}
)
expected_value = {
"status": "Completed",
"sales_order": so.name,
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
"qty": 10,
"delivered_qty": 10,
"pending_qty": 0,
"qty_to_bill": 0,
"billed_qty": 10,
"time_taken_to_deliver": 86400,
}
self.assertEqual(len(data), 1)
for key, val in expected_value.items():
with self.subTest(key=key, val=val):
self.assertEqual(data[0][key], val)
def test_05_all_so_status(self):
columns, data, message, chart = execute(
{
"company": "_Test Company",
"from_date": "2021-06-01",
"to_date": "2021-06-30",
}
)
# SO's from first 4 test cases should be in output
self.assertEqual(len(data), 4)

View File

@@ -233,7 +233,8 @@ erpnext.company.setup_queries = function(frm) {
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}]
["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}],
["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}]
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});

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