Compare commits

...

140 Commits

Author SHA1 Message Date
Frappe PR Bot
42ccc092fe chore(release): Bumped to Version 14.46.1
## [14.46.1](https://github.com/frappe/erpnext/compare/v14.46.0...v14.46.1) (2023-11-01)

### Bug Fixes

* fetch asset received but not billed account only when needed ([487aa35](487aa35ee3))
2023-11-01 06:43:35 +00:00
Deepesh Garg
222bce62bf Merge pull request #37814 from frappe/mergify/bp/version-14/pr-37812
fix: fetch asset received but not billed account only when needed (#37737)
2023-11-01 12:12:02 +05:30
Deepesh Garg
487aa35ee3 fix: fetch asset received but not billed account only when needed
(cherry picked from commit 3a8736374c)
(cherry picked from commit 7df4009dd3)
2023-11-01 06:41:33 +00:00
Frappe PR Bot
0f848fd968 chore(release): Bumped to Version 14.46.0
# [14.46.0](https://github.com/frappe/erpnext/compare/v14.45.4...v14.46.0) (2023-10-31)

### Bug Fixes

* add regional support to extend purchase gl entries ([d02ebd6](d02ebd6215))
* avoid name clash in delivery stop (backport [#37306](https://github.com/frappe/erpnext/issues/37306)) ([#37701](https://github.com/frappe/erpnext/issues/37701)) ([556095d](556095daaa))
* Cash flow mapping fix ([#37522](https://github.com/frappe/erpnext/issues/37522)) ([8e31379](8e31379ecc))
* close employee loan on write off ([#37638](https://github.com/frappe/erpnext/issues/37638)) ([922ace4](922ace4076))
* **defaults:** apply discount and provisonal defaults from item group and brand if available (backport [#37466](https://github.com/frappe/erpnext/issues/37466)) ([#37703](https://github.com/frappe/erpnext/issues/37703)) ([a0893dd](a0893ddf96))
* **delivery:** rename dt fetch stop action (backport [#37605](https://github.com/frappe/erpnext/issues/37605)) ([#37606](https://github.com/frappe/erpnext/issues/37606)) ([8660faa](8660faaa54))
* GL Entries for receiving non CWIP assets using Purchase Receipt ([#37660](https://github.com/frappe/erpnext/issues/37660)) ([80774e2](80774e2da1))
* incorrect cost center in the purchase invoice (backport [#37591](https://github.com/frappe/erpnext/issues/37591)) ([#37609](https://github.com/frappe/erpnext/issues/37609)) ([50daf70](50daf701dd))
* incorrect material request quantity in production plan ([#37785](https://github.com/frappe/erpnext/issues/37785)) ([25718d9](25718d9f1b))
* incorrect process loss validation for multiple finished items (backport [#37576](https://github.com/frappe/erpnext/issues/37576)) ([#37656](https://github.com/frappe/erpnext/issues/37656)) ([638c271](638c271d70))
* indexing on Delivery Note Item (backport [#37766](https://github.com/frappe/erpnext/issues/37766)) ([#37777](https://github.com/frappe/erpnext/issues/37777)) ([9b66a06](9b66a06c86))
* make changes that enable gantt view for job cards (backport [#37661](https://github.com/frappe/erpnext/issues/37661)) ([#37756](https://github.com/frappe/erpnext/issues/37756)) ([712ddb7](712ddb75be))
* **minor:** filter bank accounts in bank statement import ([#37525](https://github.com/frappe/erpnext/issues/37525)) ([1cb9f4c](1cb9f4cf8b))
* **minor:** filter tax template based on company in subscription ([#37562](https://github.com/frappe/erpnext/issues/37562)) ([c05e0a4](c05e0a4ffc))
* **minor:** set tax values for item variants (backport [#37674](https://github.com/frappe/erpnext/issues/37674)) ([#37738](https://github.com/frappe/erpnext/issues/37738)) ([fabcfc1](fabcfc1fce))
* negative current qty causing recursion issue ([#37752](https://github.com/frappe/erpnext/issues/37752)) ([f1407bc](f1407bcfd2))
* overallocation on Payment with PO/SO ([d71b885](d71b885fb8))
* purchase receipt with stock and asset items ([375be8c](375be8cd93))
* remove from or target warehouse for non internal transfer entries (backport [#37612](https://github.com/frappe/erpnext/issues/37612)) ([#37628](https://github.com/frappe/erpnext/issues/37628)) ([78b7c26](78b7c26420))
* set correct `purchase_sle` in `get_last_sle()` ([#37708](https://github.com/frappe/erpnext/issues/37708)) ([86cf156](86cf156968))
* set empty value for tax template in item details ([#37496](https://github.com/frappe/erpnext/issues/37496)) ([ec208b8](ec208b8df5))
* typeerror on tds payable monthly report ([fea27d5](fea27d5e2e))
* update existing doc if possible ([89f07dc](89f07dcfac))
* wrong german translation ([#37658](https://github.com/frappe/erpnext/issues/37658)) ([fa5780c](fa5780ca81))

### Features

* allow return of components for SCO that don't have SCR created (backport [#37686](https://github.com/frappe/erpnext/issues/37686)) ([#37692](https://github.com/frappe/erpnext/issues/37692)) ([e96d5b3](e96d5b314a))
* **delivery:** link to delivery notes list view from delivery trip (backport [#37604](https://github.com/frappe/erpnext/issues/37604)) ([#37695](https://github.com/frappe/erpnext/issues/37695)) ([c58fefb](c58fefb359))

### Reverts

* Revert "fix: set empty value for tax template in item details (#37496)" ([ca13816](ca13816ca1)), closes [#37496](https://github.com/frappe/erpnext/issues/37496) [#37496](https://github.com/frappe/erpnext/issues/37496)
2023-10-31 12:45:11 +00:00
rohitwaghchaure
c117ab851a Merge pull request #37788 from frappe/version-14-hotfix
chore: release v14
2023-10-31 18:13:28 +05:30
rohitwaghchaure
b7b62a8966 Merge branch 'version-14' into version-14-hotfix 2023-10-31 17:38:42 +05:30
Samuel Danieli
86cf156968 fix: set correct purchase_sle in get_last_sle() (#37708)
sle_dict may look like this:
{
  'incoming': [
    {... Stock Entry ...},
    {... Purchase Receipt ...}
  ],
  'outgoing': [
    {... Stock Entry ...}
  ]
}
2023-10-31 16:07:10 +05:30
rohitwaghchaure
25718d9f1b fix: incorrect material request quantity in production plan (#37785) 2023-10-31 15:12:35 +05:30
mergify[bot]
9b66a06c86 fix: indexing on Delivery Note Item (backport #37766) (#37777)
fix: indexing on Delivery Note Item (#37766)

fix: added indexing on Delivery Note Item
(cherry picked from commit 056b74b162)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-31 14:31:38 +05:30
ruthra kumar
7c2bd24d0b Merge pull request #37765 from frappe/mergify/bp/version-14/pr-37761
chore: add index to posting_date in PLE (backport #37761)
2023-10-30 17:39:53 +05:30
ruthra kumar
af57cc6c5f Merge pull request #37762 from frappe/mergify/bp/version-14-hotfix/pr-37761
chore: add index to posting_date in PLE (backport #37761)
2023-10-30 17:31:08 +05:30
ruthra kumar
25448fcdbc chore: add index to posting_date in PLE
(cherry picked from commit ca69845238)
2023-10-30 17:01:38 +05:30
ruthra kumar
69e83ff6ab chore: add index to posting_date in PLE
(cherry picked from commit ca69845238)
2023-10-30 16:59:56 +05:30
ruthra kumar
87a8c8d08e Merge pull request #37760 from frappe/mergify/bp/version-14-hotfix/pr-37720
refactor: ignore cancelled GLE's while looking for currency of existing entries (backport #37720)
2023-10-30 16:52:50 +05:30
ruthra kumar
4c01128827 refactor: ignore cancelled GLE's while looking for currency
(cherry picked from commit 8d9b90f3f5)
2023-10-30 10:43:02 +00:00
mergify[bot]
712ddb75be fix: make changes that enable gantt view for job cards (backport #37661) (#37756)
fix: make changes that enable gantt view for job cards (#37661)

* fix: make changes that enable gantt view for job cards

* fix: add fields on listview and remove from json file

* fix: undo modified date

---------

Co-authored-by: Dietmar Fischer <fischer@kk-software.de>
(cherry picked from commit 500435b856)

Co-authored-by: Didiman1998 <118364772+Didiman1998@users.noreply.github.com>
2023-10-30 15:20:26 +05:30
Frappe PR Bot
78a9edf6c9 chore(release): Bumped to Version 14.45.4
## [14.45.4](https://github.com/frappe/erpnext/compare/v14.45.3...v14.45.4) (2023-10-30)

### Bug Fixes

* negative current qty causing recursion issue (backport [#37752](https://github.com/frappe/erpnext/issues/37752)) ([#37753](https://github.com/frappe/erpnext/issues/37753)) ([20ca948](20ca948e6b))
2023-10-30 08:55:38 +00:00
mergify[bot]
20ca948e6b fix: negative current qty causing recursion issue (backport #37752) (#37753)
fix: negative current qty causing recursion issue (#37752)

(cherry picked from commit f1407bcfd2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-30 14:23:39 +05:30
rohitwaghchaure
f1407bcfd2 fix: negative current qty causing recursion issue (#37752) 2023-10-30 13:53:48 +05:30
mergify[bot]
b605b08ec1 refactor: remove extraneous disabled filters (backport #37732) (#37748)
* refactor: remove extraneous disabled filters

(cherry picked from commit f276fbba4f)

# Conflicts:
#	erpnext/accounts/report/profitability_analysis/profitability_analysis.js
#	erpnext/public/js/controllers/accounts.js

* chore: `conflicts`

---------

Co-authored-by: Bernd Oliver Sünderhauf <46800703+bosue@users.noreply.github.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-30 10:10:16 +05:30
mergify[bot]
fabcfc1fce fix(minor): set tax values for item variants (backport #37674) (#37738)
* fix: copy all child fields to item variant

(cherry picked from commit 5deba1b6f9)

* fix: only update if variant table empty

(cherry picked from commit d436a40739)

---------

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
2023-10-29 12:16:34 +05:30
Anand Baburajan
cfb9d8c6be Merge pull request #37730 from frappe/mergify/copy/version-14/pr-37725
chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (copy #37725)
2023-10-27 19:31:38 +05:30
mergify[bot]
6a6a5b0a75 chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (#37725)
* chore: allow wip_composite_asset in the MR PO PR PI flow

(cherry picked from commit 0e5bea33a3)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* chore: resolve conflict

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 3f296eea3a)
2023-10-27 13:26:16 +00:00
mergify[bot]
3f296eea3a chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (#37725)
* chore: allow wip_composite_asset in the MR PO PR PI flow

(cherry picked from commit 0e5bea33a3)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* chore: resolve conflict

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
2023-10-27 18:54:47 +05:30
Frappe PR Bot
8e4e4a9fb4 chore(release): Bumped to Version 14.45.3
## [14.45.3](https://github.com/frappe/erpnext/compare/v14.45.2...v14.45.3) (2023-10-27)

### Bug Fixes

* typeerror on tds payable monthly report ([52cfe3f](52cfe3f612))
2023-10-27 03:54:13 +00:00
ruthra kumar
254dd3d9bf Merge pull request #37715 from frappe/mergify/bp/version-14/pr-37714
fix: typeerror on tds payable monthly report (backport #37714)
2023-10-27 09:22:22 +05:30
Frappe PR Bot
83b3785202 chore(release): Bumped to Version 14.45.2
## [14.45.2](https://github.com/frappe/erpnext/compare/v14.45.1...v14.45.2) (2023-10-27)

### Bug Fixes

* purchase receipt with stock and asset items ([848928e](848928e7d1))
2023-10-27 03:44:35 +00:00
Deepesh Garg
b017f4a817 Merge pull request #37712 from frappe/mergify/bp/version-14/pr-37705
fix: purchase receipt with stock and asset items (#37705)
2023-10-27 09:12:57 +05:30
ruthra kumar
52cfe3f612 fix: typeerror on tds payable monthly report
(cherry picked from commit fea27d5e2e)
2023-10-27 03:23:30 +00:00
ruthra kumar
fd21dcd3b5 Merge pull request #37714 from ruthra-kumar/fix_typeerror_in_tds_montly_report
fix: typeerror on tds payable monthly report
2023-10-27 08:52:11 +05:30
ruthra kumar
fea27d5e2e fix: typeerror on tds payable monthly report 2023-10-27 07:41:30 +05:30
Smit Vora
848928e7d1 fix: purchase receipt with stock and asset items
(cherry picked from commit 375be8cd93)
2023-10-26 17:53:09 +00:00
Deepesh Garg
0f4e50185e Merge pull request #37705 from vorasmit/backport-asserts
fix: purchase receipt with stock and asset items
2023-10-26 23:22:18 +05:30
mergify[bot]
a0893ddf96 fix(defaults): apply discount and provisonal defaults from item group and brand if available (backport #37466) (#37703)
fix(defaults): apply discount and provisonal defaults from item group and brand if available (#37466)

(cherry picked from commit 1612d7ba3f)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 18:14:23 +05:30
mergify[bot]
556095daaa fix: avoid name clash in delivery stop (backport #37306) (#37701)
fix: avoid name clash in delivery stop (#37306)

* fix(stock): avoid name clash in delivery stop with Document.lock()

* chore(stock): format delivery stop json according to doctype builder

(cherry picked from commit 681782121c)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 18:12:01 +05:30
Smit Vora
375be8cd93 fix: purchase receipt with stock and asset items 2023-10-26 17:59:42 +05:30
Frappe PR Bot
38665760cd chore(release): Bumped to Version 14.45.1
## [14.45.1](https://github.com/frappe/erpnext/compare/v14.45.0...v14.45.1) (2023-10-26)

### Bug Fixes

* add regional support to extend purchase gl entries ([7558b62](7558b622a4))
* update existing doc if possible ([e457d39](e457d39b5d))
2023-10-26 08:52:24 +00:00
Deepesh Garg
f5120a6dd0 Merge pull request #37699 from frappe/mergify/bp/version-14/pr-37687
fix: add regional support to extend purchase gl entries (#37595)
2023-10-26 14:20:48 +05:30
Deepesh Garg
519bf3d377 chore: resolve conflicts
(cherry picked from commit fa490ef2f0)
2023-10-26 08:45:32 +00:00
Deepesh Garg
cef2231d6a chore: resolve conflicts
(cherry picked from commit e01e9ebc37)
2023-10-26 08:45:32 +00:00
Smit Vora
e457d39b5d fix: update existing doc if possible
(cherry picked from commit ff7108a3b1)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
(cherry picked from commit 89f07dcfac)
2023-10-26 08:45:32 +00:00
Smit Vora
7558b622a4 fix: add regional support to extend purchase gl entries
(cherry picked from commit 77cc91d06b)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/controllers/stock_controller.py
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
(cherry picked from commit d02ebd6215)
2023-10-26 08:45:31 +00:00
mergify[bot]
c58fefb359 feat(delivery): link to delivery notes list view from delivery trip (backport #37604) (#37695)
feat(delivery): link to delivery notes list view from delivery trip

(cherry picked from commit 85488cd0dc)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 13:12:44 +05:30
mergify[bot]
e96d5b314a feat: allow return of components for SCO that don't have SCR created (backport #37686) (#37692)
* feat: allow return of components for SCO that don't have SCR created

(cherry picked from commit 8e3b9ec879)

* fix: consider returned qty while calculating unsupplied qty

(cherry picked from commit 3290df5593)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-26 06:14:02 +00:00
Deepesh Garg
a615535ca5 Merge pull request #37687 from frappe/mergify/bp/version-14-hotfix/pr-37595
fix: add regional support to extend purchase gl entries (#37595)
2023-10-25 23:09:28 +05:30
Deepesh Garg
fa490ef2f0 chore: resolve conflicts 2023-10-25 19:46:51 +05:30
Deepesh Garg
e01e9ebc37 chore: resolve conflicts 2023-10-25 19:42:02 +05:30
Smit Vora
89f07dcfac fix: update existing doc if possible
(cherry picked from commit ff7108a3b1)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2023-10-25 14:05:19 +00:00
Smit Vora
d02ebd6215 fix: add regional support to extend purchase gl entries
(cherry picked from commit 77cc91d06b)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/controllers/stock_controller.py
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2023-10-25 14:05:17 +00:00
mergify[bot]
f5e9913746 refactor: rename field Over Order Allowance to Blanket Order Allowance (backport #37669) (#37681)
* refactor: rename field `Over Order Allowance` to `Blanket Order Allowance`

(cherry picked from commit 8ffa2bfe25)

* chore: patch to rename field `over_order_allowance`

(cherry picked from commit fcfcf6957e)

# Conflicts:
#	erpnext/patches.txt

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-25 09:55:24 +00:00
mergify[bot]
a7b75a4cc1 chore: fixed test case non_internal_transfer_delivery_note (backport #37671) (#37675)
chore: fixed test case non_internal_transfer_delivery_note (#37671)

(cherry picked from commit 2bcff4c7f2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-25 13:50:01 +05:30
rohitwaghchaure
6b951bd17a chore(release): Bumped to Version 14.45.0 2023-10-25 12:30:31 +05:30
ruthra kumar
912c315286 Merge pull request #37667 from frappe/mergify/bp/version-14-hotfix/pr-37625
refactor: set exchange rate on foreign currency JE from Bank Reconciliation (backport #37625)
2023-10-25 12:24:37 +05:30
Frappe PR Bot
f35a0c227d chore: release v14 (#37654)
* fix(delivery): rename dt fetch stop action (backport #37605) (#37606)

fix(delivery): rename dt fetch stop action

(cherry picked from commit 79d51a0a0b)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>

* fix: incorrect cost center in the purchase invoice (backport #37591) (#37609)

* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* fix(minor): filter bank accounts in bank statement import (#37525)

fix(minor): filter bank accounts in bank statement import (#37525)

fix: filter by company in bank account
(cherry picked from commit 9d392970f0)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>

* fix: set empty value for tax template in item details (#37496)

* fix: set empty value for tax template in item details (#37496)

* fix: empty tax template for items with invalid templates

* fix: test for empty tax template

* fix: test for item tax template calculation

* fix: test for pos inv tax template calculation

(cherry picked from commit b0d440c34b)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py

* chore: resolve conflicts

---------

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>

* fix(minor): filter tax template based on company in subscription (#37562)

fix: filter tax template based on company

(cherry picked from commit 1a2f659de2)

Co-authored-by: Gursheen Anand <gursheen@frappe.io>

* fix: Cash flow mapping fix (#37522)

Cash flow mapping fix

* fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37628)

fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* Revert "fix: set empty value for tax template in item details (#37496)"

Revert "fix: set empty value for tax template in item details (#37496)"

This reverts commit ec208b8df5.

* refactor: gain_loss posting date fields in the allocation table

(cherry picked from commit 55dbcee36a)

* refactor: introduce fields in popup

(cherry picked from commit 5323bb7bee)

* refactor: pass gain loss posting date to controller

(cherry picked from commit 7e600a6494)

* test: varying posting date for gain loss journal

(cherry picked from commit 514d5434a3)

* fix: overallocation on Payment with PO/SO

(cherry picked from commit 23df4205f8)

# Conflicts:
#	erpnext/accounts/utils.py

* test: overalloction on reconciliation when PO is involved

(cherry picked from commit 946228d783)

* refactor(test): make use of utility methods

(cherry picked from commit 547993f801)

* chore: fix flakiness `test_sales_order_partial_advance_payment`

(cherry picked from commit 4dff2c7a0d)

* chore: resolve conflict

* fix: close employee loan on write off (#37638)

* fix: exclude written off amount while calculating loan repayment

* fix: revert exclude written off amount while calculating loan repayment

* fix: close employee loan on write off

* refactor: button on PE to filter associated Journals

(cherry picked from commit 150728deaa)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js

* chore: resolve conflict

* fix: incorrect process loss validation for multiple finished items (backport #37576) (#37656)

fix: incorrect process loss validation for multiple finished items (#37576)

(cherry picked from commit 92cbe580e6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* chore: fixed test cases related to Internal Transfer (#37659)

* fix: GL Entries for receiving non CWIP assets using Purchase Receipt (#37660)

* fix: GL Entries for receiving non CWIP assets using Purchase Receipt

* fix: rearrange functions

* chore: rearrange functions

* chore: rearrange functions

* fix: Purchase Invoice GL entires for assets

* test: cwip accounting unit tests

* chore: Attribute error

* chore: Purchase Invoice tests

* chore: Missing asset account

* chore: Missing asset account

* chore: update tests

* fix: Internal transfer GL Entries

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* chore: make `Reserve Stock` checkbox visible in SO

* refactor: rename field `Auto Reserve Stock for Sales Order`

* feat: add fields to hold SO and SO Item ref in PR Item

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* refactor: Remove expense included in valuation accounts

* chore: Add back default in transit warehousefield

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>

* fix: wrong german translation (#37658)

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: David Arnold <dgx.arnold@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
Co-authored-by: Gursheen Anand <gursheen@frappe.io>
Co-authored-by: saeedkola <mohammedsaeedk@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-10-25 12:03:24 +05:30
ruthra kumar
2317f6a000 chore: resolve conflict 2023-10-25 11:27:51 +05:30
ruthra kumar
91130854d8 refactor: handle bank transaction in foreign currency
(cherry picked from commit 74a0d6408a)
2023-10-25 05:39:51 +00:00
ruthra kumar
677d728e67 refactor: exc rate on foreign currency JE from Bank Reconciliation
(cherry picked from commit 89f484282a)

# Conflicts:
#	erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
2023-10-25 05:39:51 +00:00
Raffael Meyer
fa5780ca81 fix: wrong german translation (#37658) 2023-10-25 09:13:06 +05:30
Deepesh Garg
80774e2da1 fix: GL Entries for receiving non CWIP assets using Purchase Receipt (#37660)
* fix: GL Entries for receiving non CWIP assets using Purchase Receipt

* fix: rearrange functions

* chore: rearrange functions

* chore: rearrange functions

* fix: Purchase Invoice GL entires for assets

* test: cwip accounting unit tests

* chore: Attribute error

* chore: Purchase Invoice tests

* chore: Missing asset account

* chore: Missing asset account

* chore: update tests

* fix: Internal transfer GL Entries

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* chore: make `Reserve Stock` checkbox visible in SO

* refactor: rename field `Auto Reserve Stock for Sales Order`

* feat: add fields to hold SO and SO Item ref in PR Item

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* refactor: Remove expense included in valuation accounts

* chore: Add back default in transit warehousefield

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-25 09:12:27 +05:30
rohitwaghchaure
72d32a4901 chore: fixed test cases related to Internal Transfer (#37659) 2023-10-24 19:10:23 +05:30
mergify[bot]
638c271d70 fix: incorrect process loss validation for multiple finished items (backport #37576) (#37656)
fix: incorrect process loss validation for multiple finished items (#37576)

(cherry picked from commit 92cbe580e6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-24 18:03:34 +05:30
ruthra kumar
356b1bdb38 Merge pull request #37647 from frappe/mergify/copy/version-14-hotfix/pr-37200
refactor: button in Payment Entry to filter associated Journals (copy #37200)
2023-10-24 10:23:53 +05:30
ruthra kumar
0b082f0edc chore: resolve conflict 2023-10-24 09:44:41 +05:30
ruthra kumar
be90be37c7 refactor: button on PE to filter associated Journals
(cherry picked from commit 150728deaa)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js
2023-10-24 04:09:06 +00:00
Anand Baburajan
922ace4076 fix: close employee loan on write off (#37638)
* fix: exclude written off amount while calculating loan repayment

* fix: revert exclude written off amount while calculating loan repayment

* fix: close employee loan on write off
2023-10-24 09:38:08 +05:30
ruthra kumar
920de792ce Merge pull request #37646 from frappe/mergify/bp/version-14-hotfix/pr-37586
fix: overallocation on purchase order to multiple invoices (backport #37586)
2023-10-24 09:22:19 +05:30
ruthra kumar
d576fc7490 chore: resolve conflict 2023-10-24 08:48:53 +05:30
ruthra kumar
7b9daeff66 chore: fix flakiness test_sales_order_partial_advance_payment
(cherry picked from commit 4dff2c7a0d)
2023-10-24 03:14:52 +00:00
ruthra kumar
c921d7d409 refactor(test): make use of utility methods
(cherry picked from commit 547993f801)
2023-10-24 03:14:51 +00:00
ruthra kumar
c8922ad566 test: overalloction on reconciliation when PO is involved
(cherry picked from commit 946228d783)
2023-10-24 03:14:51 +00:00
ruthra kumar
d71b885fb8 fix: overallocation on Payment with PO/SO
(cherry picked from commit 23df4205f8)

# Conflicts:
#	erpnext/accounts/utils.py
2023-10-24 03:14:51 +00:00
ruthra kumar
f413530493 Merge pull request #37643 from frappe/mergify/bp/version-14-hotfix/pr-37630
refactor: configurable exchange gain or loss posting date (backport #37630)
2023-10-24 05:50:57 +05:30
ruthra kumar
ae788e8358 test: varying posting date for gain loss journal
(cherry picked from commit 514d5434a3)
2023-10-23 15:57:16 +00:00
ruthra kumar
515bed8c80 refactor: pass gain loss posting date to controller
(cherry picked from commit 7e600a6494)
2023-10-23 15:57:16 +00:00
ruthra kumar
063d658b04 refactor: introduce fields in popup
(cherry picked from commit 5323bb7bee)
2023-10-23 15:57:15 +00:00
ruthra kumar
fa7fa85d92 refactor: gain_loss posting date fields in the allocation table
(cherry picked from commit 55dbcee36a)
2023-10-23 15:57:14 +00:00
Deepesh Garg
ca13816ca1 Revert "fix: set empty value for tax template in item details (#37496)"
Revert "fix: set empty value for tax template in item details (#37496)"

This reverts commit ec208b8df5.
2023-10-23 15:11:33 +05:30
mergify[bot]
78b7c26420 fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37628)
fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-23 12:27:01 +05:30
saeedkola
8e31379ecc fix: Cash flow mapping fix (#37522)
Cash flow mapping fix
2023-10-23 11:14:06 +05:30
mergify[bot]
c05e0a4ffc fix(minor): filter tax template based on company in subscription (#37562)
fix: filter tax template based on company

(cherry picked from commit 1a2f659de2)

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
2023-10-23 11:01:23 +05:30
mergify[bot]
ec208b8df5 fix: set empty value for tax template in item details (#37496)
* fix: set empty value for tax template in item details (#37496)

* fix: empty tax template for items with invalid templates

* fix: test for empty tax template

* fix: test for item tax template calculation

* fix: test for pos inv tax template calculation

(cherry picked from commit b0d440c34b)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py

* chore: resolve conflicts

---------

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-10-23 10:15:46 +05:30
mergify[bot]
1cb9f4cf8b fix(minor): filter bank accounts in bank statement import (#37525)
fix(minor): filter bank accounts in bank statement import (#37525)

fix: filter by company in bank account
(cherry picked from commit 9d392970f0)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-10-23 10:04:02 +05:30
mergify[bot]
50daf701dd fix: incorrect cost center in the purchase invoice (backport #37591) (#37609)
* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-20 17:02:56 +05:30
mergify[bot]
8660faaa54 fix(delivery): rename dt fetch stop action (backport #37605) (#37606)
fix(delivery): rename dt fetch stop action

(cherry picked from commit 79d51a0a0b)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-20 16:12:42 +05:30
Frappe PR Bot
6931db98f1 chore(release): Bumped to Version 14.44.1
## [14.44.1](https://github.com/frappe/erpnext/compare/v14.44.0...v14.44.1) (2023-10-19)

### Bug Fixes

* billed_qty to show a sum of all invoiced qty from the purchase order item. (backport [#37539](https://github.com/frappe/erpnext/issues/37539)) ([#37558](https://github.com/frappe/erpnext/issues/37558)) ([ac7d6d6](ac7d6d6d59))
* consider received qty while creating SO -> MR (backport [#37414](https://github.com/frappe/erpnext/issues/37414)) ([#37514](https://github.com/frappe/erpnext/issues/37514)) ([1b94510](1b94510f08))
* don't set finance books if gross_purchase_amount is not set (backport [#37480](https://github.com/frappe/erpnext/issues/37480)) ([#37482](https://github.com/frappe/erpnext/issues/37482)) ([0590f21](0590f21814))
* e-commerce permissions for address ([#37554](https://github.com/frappe/erpnext/issues/37554)) ([022f85d](022f85dd08))
* german tranlations of "Is Return" ([f9b2355](f9b2355066))
* GL Entries not getting created for PR Return (backport [#37513](https://github.com/frappe/erpnext/issues/37513)) ([#37516](https://github.com/frappe/erpnext/issues/37516)) ([c32258e](c32258e4b6))
* **gp:** wrong `allocated_amount` on multi sales person invoice ([d266423](d266423011))
* Incorrect vat amount in KSA VAT report ([44f7de0](44f7de0f31))
* inflated total amt in TDS report using back calculation ([78e22af](78e22af3ca))
* Issues related to RFQ and Supplier Quotation on Portal (backport [#37565](https://github.com/frappe/erpnext/issues/37565)) ([#37577](https://github.com/frappe/erpnext/issues/37577)) ([e1504ef](e1504efd40))
* keep customer/supplier website role by default ([76ef61c](76ef61c24f))
* keyerror on gl and pl comparision report ([6f143d3](6f143d35aa))
* payment entry count on supplier dashboard (backport [#37571](https://github.com/frappe/erpnext/issues/37571)) ([#37575](https://github.com/frappe/erpnext/issues/37575)) ([95abd79](95abd7908f))
* same Serial No get mapped while creating SO -> DN ([#37527](https://github.com/frappe/erpnext/issues/37527)) ([5025850](5025850258))
* serial and batch no get removed on save of return DN ([#37476](https://github.com/frappe/erpnext/issues/37476)) ([f1814a1](f1814a1a2a))
* Stock Reconciliation Insufficient Stock Error ([#37494](https://github.com/frappe/erpnext/issues/37494)) ([9406ddb](9406ddbff0))
* **test:** project test case (backport [#37541](https://github.com/frappe/erpnext/issues/37541)) ([#37543](https://github.com/frappe/erpnext/issues/37543)) ([e23710b](e23710bf00))
* use `flt` to ignore TypeError ([#37481](https://github.com/frappe/erpnext/issues/37481)) ([d2b22db](d2b22db500))

### Performance Improvements

* index `dn_detail` in `Delivery Note Item` (backport [#37528](https://github.com/frappe/erpnext/issues/37528)) ([#37530](https://github.com/frappe/erpnext/issues/37530)) ([001c230](001c230688))
2023-10-19 11:36:28 +00:00
Deepesh Garg
fdb2e94b5c Merge pull request #37545 from frappe/version-14-hotfix
chore: release v14
2023-10-19 17:04:34 +05:30
mergify[bot]
e1504efd40 fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (#37577)
* fix: Issues related to RFQ and Supplier Quotation on Portal (#37565)

fix: RFQ and Supplier Quotation for Portal
(cherry picked from commit 2851a41310)

* chore: removed backport changes

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 13:50:55 +05:30
mergify[bot]
95abd7908f fix: payment entry count on supplier dashboard (backport #37571) (#37575)
fix: payment entry count on supplier dashboard (#37571)

(cherry picked from commit 10311ff114)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 13:38:24 +05:30
mergify[bot]
022f85dd08 fix: e-commerce permissions for address (#37554)
* fix: E-commerce permissions

(cherry picked from commit f4d74990fe)

# Conflicts:
#	erpnext/controllers/selling_controller.py

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-10-18 17:21:46 +05:30
Deepesh Garg
837a6b5f50 Merge pull request #37555 from frappe/mergify/bp/version-14-hotfix/pr-37550
chore: Add accounting dimensions to Sales Order Item table (backport #37550)
2023-10-18 17:09:14 +05:30
ruthra kumar
f6a550faae Merge pull request #37569 from frappe/mergify/bp/version-14-hotfix/pr-37105
refactor: move `unreconcile` button into a drop down (backport #37105)
2023-10-18 16:31:52 +05:30
ruthra kumar
54f672e144 refactor: add unreconcile btn to purchase invoice
(cherry picked from commit 94ce43b0d5)
2023-10-18 10:58:20 +00:00
ruthra kumar
e8d082560a refactor: move unreconcile btn inside a drop down
(cherry picked from commit f2b0ac6868)
2023-10-18 10:58:19 +00:00
mergify[bot]
ac7d6d6d59 fix: billed_qty to show a sum of all invoiced qty from the purchase order item. (backport #37539) (#37558)
fix: billed_qty to show a sum of all invoiced qty from the purchase order item.

(cherry picked from commit 8a72f4f58a)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2023-10-18 06:00:29 +00:00
ruthra kumar
e2e89492e0 Merge pull request #37556 from frappe/mergify/bp/version-14-hotfix/pr-37549
refactor: use account in key while grouping voucher in ar/ap report (backport #37549)
2023-10-18 09:41:24 +05:30
ruthra kumar
760eab961d test: report output if party is missing
(cherry picked from commit 244cec64b2)
2023-10-18 03:40:35 +00:00
ruthra kumar
3499089323 refactor: use account in key while grouping voucher in ar/ap report
(cherry picked from commit 601ab4567e)
2023-10-18 03:40:34 +00:00
Deepesh Garg
7db6988364 chore: resolve conflicts 2023-10-18 09:00:49 +05:30
Deepesh Garg
bfa93cd3f6 chore: Add accounting dimensions to Sales Order Item table
(cherry picked from commit e31db18912)

# Conflicts:
#	erpnext/patches.txt
2023-10-17 17:25:57 +00:00
mergify[bot]
e23710bf00 fix(test): project test case (backport #37541) (#37543)
fix(test): project test case

(cherry picked from commit fd6aee15e6)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-17 15:03:16 +05:30
s-aga-r
5025850258 fix: same Serial No get mapped while creating SO -> DN (#37527)
* fix: same Serial No get mapped while creating SO -> DN

* test: add test case for DN with repetitive serial item
2023-10-17 12:20:23 +05:30
ruthra kumar
473610506c Merge pull request #37540 from frappe/mergify/bp/version-14-hotfix/pr-37330
refactor: checkbox to toggle exchange rate inheritence in PO->PI (backport #37330)
2023-10-17 10:55:27 +05:30
ruthra kumar
71cb7d37ee refactor: checkbox to toggle exchange rate inheritence in PO->PI
(cherry picked from commit 08315522bb)
2023-10-17 04:16:50 +00:00
Deepesh Garg
5b1016c17d Merge pull request #37524 from deepeshgarg007/ksa_vat
fix: Incorrect vat amount in KSA VAT report
2023-10-16 18:41:25 +05:30
Ankush Menat
d598dad50e Merge pull request #37533 from frappe/mergify/bp/version-14-hotfix/pr-37532
fix: keep customer/supplier website role by default (backport #37532)
2023-10-16 17:33:44 +05:30
Ankush Menat
76ef61c24f fix: keep customer/supplier website role by default
(cherry picked from commit d2096cfdb7)
2023-10-16 12:01:17 +00:00
mergify[bot]
001c230688 perf: index dn_detail in Delivery Note Item (backport #37528) (#37530)
* perf: index `dn_detail` in `Delivery Note Item`

(cherry picked from commit 5b4528e614)

# Conflicts:
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 16:48:55 +05:30
Deepesh Garg
44f7de0f31 fix: Incorrect vat amount in KSA VAT report 2023-10-16 14:37:16 +05:30
mergify[bot]
c32258e4b6 fix: GL Entries not getting created for PR Return (backport #37513) (#37516)
* fix: GL Entries not getting created for PR Return

(cherry picked from commit 46add06a29)

* test: add test case for PR return with zero rate

(cherry picked from commit 253d4782c6)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 01:24:10 +05:30
mergify[bot]
1b94510f08 fix: consider received qty while creating SO -> MR (backport #37414) (#37514)
fix: consider received qty while creating SO -> MR

(cherry picked from commit b2cee396ac)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 01:23:13 +05:30
ruthra kumar
65b7fb1293 Merge pull request #37511 from frappe/mergify/bp/version-14-hotfix/pr-37319
test: use fixtures for sales and purchase invoice (backport #37319)
2023-10-15 12:44:13 +05:30
ruthra kumar
d1f6d62d72 chore: fix flaky test case 2023-10-15 12:19:34 +05:30
ruthra kumar
77e7a6cde7 Merge pull request #37512 from ruthra-kumar/back_calculate_tds
fix: inflated total amt in TDS report using back calculation
2023-10-15 11:54:25 +05:30
ruthra kumar
78e22af3ca fix: inflated total amt in TDS report using back calculation 2023-10-15 11:23:52 +05:30
ruthra kumar
7f903532f3 chore: resovle conflicts 2023-10-15 10:44:37 +05:30
ruthra kumar
8d1eac89e3 refactor(test): make sure TDS Payable is available for testing
(cherry picked from commit fbabf4ac2e)
2023-10-15 04:47:43 +00:00
ruthra kumar
d78316869b refactor(test): make use of @change_settings in PI test cases
(cherry picked from commit 0207d6e7c9)
2023-10-15 04:47:43 +00:00
ruthra kumar
33becb7b32 refactor(test): use test fixture in purchase invoice
(cherry picked from commit a2e064d214)
2023-10-15 04:47:43 +00:00
ruthra kumar
b97fdbe6fc refactor(test): use test fixture in subscription
(cherry picked from commit 3bdf4f628c)

# Conflicts:
#	erpnext/accounts/doctype/subscription/test_subscription.py
2023-10-15 04:47:42 +00:00
ruthra kumar
5699a8daa2 refactor(test): use @change_settings to fix failing test cases
(cherry picked from commit de9baef84a)
2023-10-15 04:47:42 +00:00
ruthra kumar
91a5bd8615 refactor(test): fix broken test cases in Sales Invoice
(cherry picked from commit 8ebe5733ac)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:42 +00:00
ruthra kumar
485cb7dd28 refactor(test): use @change_settings in sales invoice
(cherry picked from commit 58065f31b1)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:42 +00:00
ruthra kumar
9d6b434d1f refactor(test): unset accounts frozen date
(cherry picked from commit fc50b174eb)
2023-10-15 04:47:41 +00:00
ruthra kumar
405d1528c3 test: use fixtures for sales and purchase invoice
(cherry picked from commit c322e5f381)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:41 +00:00
s-aga-r
f1814a1a2a fix: serial and batch no get removed on save of return DN (#37476)
* fix: serial and batch no get removed on save of return DN

* test: add test case for DN return with product bundle
2023-10-15 09:57:39 +05:30
s-aga-r
9406ddbff0 fix: Stock Reconciliation Insufficient Stock Error (#37494)
* fix: Stock Reconciliation Insufficient Stock Error

* fix: linter

* test: add test case for Stock Reco Batch Item
2023-10-14 16:53:29 +05:30
ruthra kumar
bae6c5bf5f Merge pull request #37500 from frappe/mergify/bp/version-14-hotfix/pr-37435
fix(gp): wrong `allocated_amount` when grouped by Sales Person (backport #37435)
2023-10-14 12:54:33 +05:30
ruthra kumar
cf9acd1ff6 Merge pull request #37501 from frappe/mergify/bp/version-14-hotfix/pr-37495
fix: keyerror on gl and pl comparision report (backport #37495)
2023-10-14 12:54:09 +05:30
ruthra kumar
6f143d35aa fix: keyerror on gl and pl comparision report
(cherry picked from commit ad00df0af6)
2023-10-14 06:46:18 +00:00
Dany Robert
d266423011 fix(gp): wrong allocated_amount on multi sales person invoice
(cherry picked from commit bda82bf1e9)
2023-10-14 06:43:15 +00:00
s-aga-r
d2b22db500 fix: use flt to ignore TypeError (#37481) 2023-10-13 10:22:20 +05:30
ruthra kumar
c43d0b81e3 Merge pull request #37488 from frappe/mergify/bp/version-14-hotfix/pr-37484
refactor(patch): ignore links on closing balance patch (backport #37484)
2023-10-13 09:59:08 +05:30
ruthra kumar
cf0ab51348 refactor(patch): ignore links on closing balance patch
(cherry picked from commit 17ca8756a7)
2023-10-13 03:50:02 +00:00
mergify[bot]
0590f21814 fix: don't set finance books if gross_purchase_amount is not set (backport #37480) (#37482)
fix: don't set finance books if gross_purchase_amount is not set (#37480)

(cherry picked from commit 18e3a8907a)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-10-12 19:44:36 +05:30
Sagar Vora
bbdf26c82e Merge pull request #37468 from frappe/mergify/bp/version-14-hotfix/pr-37418
fix: german tranlations of "Is Return" (backport #37418)
2023-10-12 13:05:49 +05:30
ruthra kumar
020d2e2ca6 Merge pull request #37464 from frappe/mergify/bp/version-14-hotfix/pr-37436
refactor: for non-repost fields, don't validate (backport #37436)
2023-10-12 10:51:42 +05:30
ruthra kumar
f434548204 Merge pull request #37465 from frappe/mergify/bp/version-14-hotfix/pr-37459
refactor: add validation for Advances in SI/PI (backport #37459)
2023-10-12 10:51:18 +05:30
barredterra
f9b2355066 fix: german tranlations of "Is Return"
(cherry picked from commit 38ca164662)
2023-10-12 02:21:05 +00:00
ruthra kumar
d37a1811db refactor: add validation for Advances in SI/PI
(cherry picked from commit 0cdd6435a5)
2023-10-11 14:34:38 +00:00
ruthra kumar
8dd26949b7 refactor: for non-repost fields, don't validate
(cherry picked from commit c1782c5015)
2023-10-11 14:34:29 +00:00
106 changed files with 2028 additions and 1807 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.44.0"
__version__ = "14.46.1"
def get_default_company(user=None):

View File

@@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
}
)
cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit()

View File

@@ -301,3 +301,30 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map
def create_accounting_dimensions_for_doctype(doctype):
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -17,6 +17,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
get_entries,
)
from erpnext.accounts.utils import get_balance_on
from erpnext.setup.utils import get_exchange_rate
class BankReconciliationTool(Document):
@@ -129,7 +130,7 @@ def create_journal_entry_bts(
bank_transaction = frappe.db.get_values(
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"],
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
as_dict=True,
)[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
@@ -143,29 +144,94 @@ def create_journal_entry_bts(
)
company = frappe.get_value("Account", company_account, "company")
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
# determine if multi-currency Journal or not
is_multi_currency = (
True
if company_default_currency != company_account_currency
or company_default_currency != second_account_currency
or company_default_currency != bank_transaction.currency
else False
)
accounts = []
# Multi Currency?
accounts.append(
{
"account": second_account,
"credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
)
second_account_dict = {
"account": second_account,
"account_currency": second_account_currency,
"credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
accounts.append(
{
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company),
}
)
company_account_dict = {
"account": company_account,
"account_currency": company_account_currency,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company),
}
# convert transaction amount to company currency
if is_multi_currency:
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
else:
withdrawal_in_company_currency = bank_transaction.withdrawal
deposit_in_company_currency = bank_transaction.deposit
# if second account is of foreign currency, convert and set debit and credit fields.
if second_account_currency != company_default_currency:
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
second_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
}
)
else:
second_account_dict.update(
{
"exchange_rate": 1,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": deposit_in_company_currency,
"debit_in_account_currency": withdrawal_in_company_currency,
}
)
# if company account is of foreign currency, convert and set debit and credit fields.
if company_account_currency != company_default_currency:
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
company_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
}
)
else:
company_account_dict.update(
{
"exchange_rate": 1,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
"credit_in_account_currency": withdrawal_in_company_currency,
"debit_in_account_currency": deposit_in_company_currency,
}
)
accounts.append(second_account_dict)
accounts.append(company_account_dict)
journal_entry_dict = {
"voucher_type": entry_type,
@@ -175,6 +241,9 @@ def create_journal_entry_bts(
"cheque_no": reference_number,
"mode_of_payment": mode_of_payment,
}
if is_multi_currency:
journal_entry_dict.update({"multi_currency": True})
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts)

View File

@@ -2,6 +2,16 @@
// For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", {
onload(frm) {
frm.set_query("bank_account", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
},
setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false;

View File

@@ -152,6 +152,12 @@ frappe.ui.form.on('Payment Entry', {
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(frm);
if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
}, __('Actions'));
}
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
},

View File

@@ -29,7 +29,8 @@
{
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
"label": "Posting Date",
"search_index": 1
},
{
"fieldname": "account_type",
@@ -147,7 +148,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-08-22 15:32:56.629430",
"modified": "2023-10-30 16:15:00.470283",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",

View File

@@ -216,6 +216,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.data = [];
const dialog = new frappe.ui.Dialog({
title: __("Select Difference Account"),
size: 'extra-large',
fields: [
{
fieldname: "allocation",
@@ -239,6 +240,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
in_list_view: 1,
read_only: 1
}, {
fieldtype:'Date',
fieldname:"gain_loss_posting_date",
label: __("Posting Date"),
in_list_view: 1,
reqd: 1,
}, {
fieldtype:'Link',
options: 'Account',
in_list_view: 1,
@@ -272,6 +280,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
args.forEach(d => {
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
"difference_account", d.difference_account);
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
"gain_loss_posting_date", d.gain_loss_posting_date);
});
this.reconcile_payment_entries();
@@ -287,6 +298,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
'reference_name': d.reference_name,
'difference_amount': d.difference_amount,
'difference_account': d.difference_account,
'gain_loss_posting_date': d.gain_loss_posting_date
});
}
});

View File

@@ -315,6 +315,7 @@ class PaymentReconciliation(Document):
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if pay.get("amount") == 0:
entries.append(res)
@@ -421,6 +422,7 @@ class PaymentReconciliation(Document):
"allocated_amount": flt(row.get("allocated_amount")),
"difference_amount": flt(row.get("difference_amount")),
"difference_account": row.get("difference_account"),
"difference_posting_date": row.get("gain_loss_posting_date"),
"cost_center": row.get("cost_center"),
}
)

View File

@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Item"]
@@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
def create_account(self):
account_name = "Debtors EUR"
if not frappe.db.get_value(
"Account", filters={"account_name": account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = account_name
acc.parent_account = "Accounts Receivable - _PR"
acc.company = self.company
acc.account_currency = "EUR"
acc.account_type = "Receivable"
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
self.debtors_eur = acc.name
accounts = [
{
"attribute": "debtors_eur",
"account_name": "Debtors EUR",
"parent_account": "Accounts Receivable - _PR",
"account_currency": "EUR",
"account_type": "Receivable",
},
{
"attribute": "creditors_usd",
"account_name": "Payable USD",
"parent_account": "Accounts Payable - _PR",
"account_currency": "USD",
"account_type": "Payable",
},
]
for x in accounts:
x = frappe._dict(x)
if not frappe.db.get_value(
"Account", filters={"account_name": x.account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = x.account_name
acc.parent_account = x.parent_account
acc.company = self.company
acc.account_currency = x.account_currency
acc.account_type = x.account_type
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": x.account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
setattr(self, x.attribute, acc.name)
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
@@ -151,6 +170,64 @@ class TestPaymentReconciliation(FrappeTestCase):
payment.posting_date = posting_date
return payment
def create_purchase_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
pinv = make_purchase_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.supplier,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return pinv
def create_purchase_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
pord = create_purchase_order(
qty=qty,
rate=rate,
company=self.company,
customer=self.supplier,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return pord
def clear_old_entries(self):
doctype_list = [
"GL Entry",
@@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_payment_reconciliation(self):
def create_payment_reconciliation(self, party_is_customer=True):
pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = (
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
)
pr.party = self.customer
pr.party_type = "Customer" if party_is_customer else "Supplier"
pr.party = self.customer if party_is_customer else self.supplier
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr
@@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.allocation[0].difference_amount, 0)
def test_reconciliation_purchase_invoice_against_return(self):
pi = make_purchase_invoice(
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
).submit()
self.supplier = "_Test Supplier USD"
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
pi.supplier = self.supplier
pi.currency = "USD"
pi.conversion_rate = 50
pi.credit_to = self.creditors_usd
pi.save().submit()
pi_return = frappe.get_doc(pi.as_dict())
pi_return.name = None
@@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
pi_return.items[0].qty = -pi_return.items[0].qty
pi_return.submit()
self.company = "_Test Company"
self.party_type = "Supplier"
self.customer = "_Test Supplier USD"
pr = self.create_payment_reconciliation()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Supplier"
pr.party = self.supplier
pr.receivable_payable_account = self.creditors_usd
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
pr.get_unreconciled_entries()
invoices = []
@@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
if invoice.invoice_number == pi.name:
invoices.append(invoice.as_dict())
break
for payment in pr.payments:
if payment.reference_name == pi_return.name:
payments.append(payment.as_dict())
@@ -941,6 +1022,121 @@ class TestPaymentReconciliation(FrappeTestCase):
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
pr.reconcile()
def test_reconciliation_from_purchase_order_to_multiple_invoices(self):
"""
Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation
"""
self.supplier = "_Test Supplier"
pi1 = self.create_purchase_invoice(qty=10, rate=100)
pi2 = self.create_purchase_invoice(qty=10, rate=100)
po = self.create_purchase_order(qty=20, rate=100)
pay = get_payment_entry(po.doctype, po.name)
# Overpay Puchase Order
pay.paid_amount = 3000
pay.save().submit()
# assert total allocated and unallocated before reconciliation
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(po.doctype, po.name, 2000),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 2)
self.assertEqual(len(pr.payments), 2)
for x in pr.payments:
self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name))
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# partial allocation on pi1 and full allocate on pi2
pr.allocation[0].allocated_amount = 100
pr.reconcile()
# assert references and total allocated and unallocated amount
pay.reload()
self.assertEqual(len(pay.references), 3)
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(po.doctype, po.name, 900),
)
self.assertEqual(
(
pay.references[1].reference_doctype,
pay.references[1].reference_name,
pay.references[1].allocated_amount,
),
(pi1.doctype, pi1.name, 100),
)
self.assertEqual(
(
pay.references[2].reference_doctype,
pay.references[2].reference_name,
pay.references[2].allocated_amount,
),
(pi2.doctype, pi2.name, 1000),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 2)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# assert references and total allocated and unallocated amount
pay.reload()
self.assertEqual(len(pay.references), 3)
# PO references should be removed now
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(pi1.doctype, pi1.name, 100),
)
self.assertEqual(
(
pay.references[1].reference_doctype,
pay.references[1].reference_name,
pay.references[1].allocated_amount,
),
(pi2.doctype, pi2.name, 1000),
)
self.assertEqual(
(
pay.references[2].reference_doctype,
pay.references[2].reference_name,
pay.references[2].allocated_amount,
),
(pi1.doctype, pi1.name, 900),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -19,6 +19,7 @@
"is_advance",
"section_break_5",
"difference_amount",
"gain_loss_posting_date",
"column_break_7",
"difference_account",
"exchange_rate",
@@ -151,11 +152,16 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
}
],
"istable": 1,
"links": [],
"modified": "2023-09-03 07:52:33.684217",
"modified": "2023-10-23 10:44:56.066303",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",

View File

@@ -181,6 +181,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
}
unblock_invoice() {

View File

@@ -36,6 +36,7 @@
"currency_and_price_list",
"currency",
"conversion_rate",
"use_transaction_date_exchange_rate",
"column_break2",
"buying_price_list",
"price_list_currency",
@@ -1578,13 +1579,20 @@
"label": "Repost Required",
"options": "Account",
"read_only": 1
},
{
"default": "0",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-10-01 21:01:47.282533",
"modified": "2023-10-16 16:24:51.886231",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import (
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import validate_account_head
@@ -284,9 +284,6 @@ class PurchaseInvoice(BuyingController):
# in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item
# except opening entry, drop-ship entry and fixed asset items
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if (
auto_accounting_for_stock
and item.item_code in stock_items
@@ -353,22 +350,26 @@ class PurchaseInvoice(BuyingController):
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
elif item.is_fixed_asset and item.pr_detail:
if not asset_received_but_not_billed:
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
item.expense_account = asset_received_but_not_billed
elif item.is_fixed_asset:
account_type = (
"capital_work_in_progress_account"
if is_cwip_accounting_enabled(item.asset_category)
else "fixed_asset_account"
)
asset_category_account = get_asset_category_account(
"fixed_asset_account", item=item.item_code, company=self.company
account_type, item=item.item_code, company=self.company
)
if not asset_category_account:
form_link = get_link_to_form("Asset Category", asset_category)
form_link = get_link_to_form("Asset Category", item.asset_category)
throw(
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
title=_("Missing Account"),
)
item.expense_account = asset_category_account
elif item.is_fixed_asset and item.pr_detail:
if not asset_received_but_not_billed:
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
item.expense_account = asset_received_but_not_billed
elif not item.expense_account and for_validate:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
@@ -546,8 +547,9 @@ class PurchaseInvoice(BuyingController):
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
@@ -590,12 +592,11 @@ class PurchaseInvoice(BuyingController):
def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if self.auto_accounting_for_stock:
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
else:
self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0
gl_entries = []
@@ -604,9 +605,6 @@ class PurchaseInvoice(BuyingController):
self.make_item_gl_entries(gl_entries)
self.make_precision_loss_gl_entry(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
@@ -710,7 +708,11 @@ class PurchaseInvoice(BuyingController):
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
if (
self.update_stock
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
# warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
@@ -825,9 +827,7 @@ class PurchaseInvoice(BuyingController):
)
)
elif not item.is_fixed_asset or (
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
):
else:
expense_account = (
item.expense_account
if (not item.enable_deferred_expense or self.is_return)
@@ -920,40 +920,6 @@ class PurchaseInvoice(BuyingController):
)
)
# If asset is bought through this document and not linked to PR
if self.update_stock and item.landed_cost_voucher_amount:
expenses_included_in_asset_valuation = self.get_company_default(
"expenses_included_in_asset_valuation"
)
# Amount added through landed-cost-voucher
gl_entries.append(
self.get_gl_dict(
{
"account": expenses_included_in_asset_valuation,
"against": expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": expense_account,
"against": expenses_included_in_asset_valuation,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
# update gross amount of asset bought through this document
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
@@ -978,11 +944,17 @@ class PurchaseInvoice(BuyingController):
(item.purchase_receipt, valuation_tax_accounts),
)
stock_rbnb = (
self.get_company_default("asset_received_but_not_billed")
if item.is_fixed_asset
else self.stock_received_but_not_billed
)
if not negative_expense_booked_in_pr:
gl_entries.append(
self.get_gl_dict(
{
"account": self.stock_received_but_not_billed,
"account": stock_rbnb,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"),
@@ -997,156 +969,12 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount, item.precision("item_tax_amount")
)
def get_asset_gl_entry(self, gl_entries):
arbnb_account = None
eiiav_account = None
asset_eiiav_currency = None
for item in self.get("items"):
if item.is_fixed_asset:
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
item_exp_acc_type = frappe.db.get_value("Account", item.expense_account, "account_type")
if not item.expense_account or item_exp_acc_type not in [
"Asset Received But Not Billed",
"Fixed Asset",
]:
if not arbnb_account:
arbnb_account = self.get_company_default("asset_received_but_not_billed")
item.expense_account = arbnb_account
if not self.update_stock:
arbnb_currency = get_account_currency(item.expense_account)
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
item=item,
)
)
if item.item_tax_amount:
if not eiiav_account or not asset_eiiav_currency:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
asset_eiiav_currency = get_account_currency(eiiav_account)
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
"project": item.project or self.project,
"credit": item.item_tax_amount,
"credit_in_account_currency": (
item.item_tax_amount
if asset_eiiav_currency == self.company_currency
else item.item_tax_amount / self.conversion_rate
),
},
item=item,
)
)
else:
cwip_account = get_asset_account(
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(
self.get_gl_dict(
{
"account": cwip_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
),
"cost_center": self.cost_center,
"project": item.project or self.project,
},
item=item,
)
)
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
if not eiiav_account or not asset_eiiav_currency:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
asset_eiiav_currency = get_account_currency(eiiav_account)
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
"credit": item.item_tax_amount,
"project": item.project or self.project,
"credit_in_account_currency": (
item.item_tax_amount
if asset_eiiav_currency == self.company_currency
else item.item_tax_amount / self.conversion_rate
),
},
item=item,
)
)
# Assets are bought through this document then it will be linked to this document
if flt(item.landed_cost_voucher_amount):
if not eiiav_account:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": cwip_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": cwip_account,
"against": eiiav_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
# update gross amount of assets bought through this document
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
return gl_entries
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
def make_stock_adjustment_entry(
self, gl_entries, item, voucher_wise_stock_value, account_currency
@@ -1848,6 +1676,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"po_detail": "purchase_order_item",
"material_request": "material_request",
"material_request_item": "material_request_item",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),

View File

@@ -5,7 +5,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
import erpnext
@@ -33,7 +33,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
test_ignore = ["Serial No"]
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
@@ -43,6 +43,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def tearDown(self):
frappe.db.rollback()
def test_purchase_invoice_received_qty(self):
"""
1. Test if received qty is validated against accepted + rejected
@@ -417,6 +420,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertEqual(tax.tax_amount, expected_values[i][1])
self.assertEqual(tax.total, expected_values[i][2])
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -471,6 +475,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_invoice_with_advance_and_multi_payment_terms(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -1209,6 +1214,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_gain_loss_with_advance_entry(self):
unlink_enabled = frappe.db.get_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
@@ -1411,6 +1417,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -1851,6 +1858,30 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
pi.load_from_db()
self.assertFalse(pi.repost_required)
def test_default_cost_center_for_purchase(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
create_cost_center(cost_center_name=c_center)
item = create_item(
"_Test Cost Center Item For Purchase",
is_stock_item=1,
buying_cost_center="_Test Cost Center Buying - _TC",
selling_cost_center="_Test Cost Center Selling - _TC",
)
pi = make_purchase_invoice(
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
)
pi.items[0].cost_center = ""
pi.set_missing_values()
pi.calculate_taxes_and_totals()
pi.save()
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
def check_gl_entries(
doc,

View File

@@ -548,8 +548,9 @@ class SalesInvoice(SellingController):
"taxes": ("account_head",),
}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def set_paid_amount(self):
paid_amount = 0.0

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import make_autoname
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
import erpnext
@@ -38,13 +38,17 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class TestSalesInvoice(unittest.TestCase):
class TestSalesInvoice(FrappeTestCase):
def setUp(self):
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def make(self):
w = frappe.copy_doc(test_records[0])
@@ -172,6 +176,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -1293,6 +1298,7 @@ class TestSalesInvoice(unittest.TestCase):
dn.submit()
return dn
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_sales_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -2491,12 +2497,6 @@ class TestSalesInvoice(unittest.TestCase):
"stock_received_but_not_billed",
"Stock Received But Not Billed - _TC1",
)
frappe.db.set_value(
"Company",
"_Test Company 1",
"expenses_included_in_valuation",
"Expenses Included In Valuation - _TC1",
)
# begin test
si = create_sales_invoice(
@@ -2534,7 +2534,7 @@ class TestSalesInvoice(unittest.TestCase):
# tear down
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
@@ -2546,6 +2546,7 @@ class TestSalesInvoice(unittest.TestCase):
)
si = frappe.copy_doc(test_records[0])
si.customer = "_Test Internal Customer 3"
si.update_stock = 1
si.set_warehouse = "Finished Goods - _TC"
si.set_target_warehouse = "Stores - _TC"
@@ -2774,6 +2775,13 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
tds_payable_account = create_account(
account_name="TDS Payable",
account_type="Tax",
parent_account="Duties and Taxes - _TC",
company="_Test Company",
)
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
@@ -3072,8 +3080,8 @@ class TestSalesInvoice(unittest.TestCase):
si.commission_rate = commission_rate
self.assertRaises(frappe.ValidationError, si.save)
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
si.posting_date = add_days(getdate(), 1)
si.save()
@@ -3082,8 +3090,6 @@ class TestSalesInvoice(unittest.TestCase):
si.posting_date = getdate()
si.submit()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_over_billing_case_against_delivery_note(self):
"""
Test a case where duplicating the item with qty = 1 in the invoice
@@ -3112,6 +3118,13 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
@change_settings(
"Accounts Settings",
{
"book_deferred_entries_via_journal_entry": 1,
"submit_journal_entries": 1,
},
)
def test_multi_currency_deferred_revenue_via_journal_entry(self):
deferred_account = create_account(
account_name="Deferred Revenue",
@@ -3119,11 +3132,6 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_expense = 1
item.item_defaults[0].deferred_revenue_account = deferred_account
@@ -3189,13 +3197,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entries = 0
acc_settings.save()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_standalone_serial_no_return(self):
si = create_sales_invoice(
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
@@ -3573,6 +3574,20 @@ def create_internal_parties():
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_customer(
customer_name="_Test Internal Customer 3",
represents_company="_Test Company",
allowed_to_interact_with="_Test Company",
)
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
create_internal_supplier(
supplier_name="_Test Internal Supplier",
represents_company="Wind Power LLC",

View File

@@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
}
};
});
frm.set_query('sales_tax_template', function () {
return {
filters: {
company: frm.doc.company
}
};
});
},
refresh: function(frm) {

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils.data import (
add_days,
add_months,
@@ -90,10 +91,14 @@ def create_parties():
customer.insert()
class TestSubscription(unittest.TestCase):
class TestSubscription(FrappeTestCase):
def setUp(self):
create_plan()
create_parties()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def test_create_subscription_with_trial_with_correct_period(self):
subscription = frappe.new_doc("Subscription")

View File

@@ -41,7 +41,7 @@ def make_gl_entries(
from_repost=from_repost,
)
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries
# Post GL Map process there may no be any GL Entries
elif gl_map:
frappe.throw(
_(

View File

@@ -5,13 +5,8 @@
from typing import Optional
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
get_address_display,
get_company_address,
get_default_address,
)
from frappe.contacts.doctype.contact.contact import get_contact_details
from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Date, Sum
@@ -133,6 +128,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
@@ -193,6 +189,8 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
*,
ignore_permissions=False
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -205,13 +203,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
)
# address display
party_details.address_display = get_address_display(party_details[billing_address_field])
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address
if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name
)
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -229,7 +231,7 @@ def set_address_details(
if shipping_address:
party_details.update(
shipping_address=shipping_address,
shipping_address_display=get_address_display(shipping_address),
shipping_address_display=render_address(shipping_address),
**get_fetch_values(doctype, "shipping_address", shipping_address)
)
@@ -238,7 +240,8 @@ def set_address_details(
party_details.update(
billing_address=party_details.company_address,
billing_address_display=(
party_details.company_address_display or get_address_display(party_details.company_address)
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
),
**get_fetch_values(doctype, "billing_address", party_details.company_address)
)
@@ -290,7 +293,34 @@ def set_contact_details(party_details, party, party_type):
}
)
else:
party_details.update(get_contact_details(party_details.contact_person))
fields = [
"name as contact_person",
"salutation",
"first_name",
"last_name",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = frappe.db.get_value(
"Contact", party_details.contact_person, fields, as_dict=True
)
contact_details.contact_display = " ".join(
filter(
None,
[
contact_details.get("salutation"),
contact_details.get("first_name"),
contact_details.get("last_name"),
],
)
)
party_details.update(contact_details)
def set_other_values(party_details, party, party_type):
@@ -429,11 +459,19 @@ def get_party_account_currency(party_type, party, company):
def get_party_gle_currency(party_type, party, company):
def generator():
existing_gle_currency = frappe.db.sql(
"""select account_currency from `tabGL Entry`
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
limit 1""",
{"company": company, "party_type": party_type, "party": party},
gl = qb.DocType("GL Entry")
existing_gle_currency = (
qb.from_(gl)
.select(gl.account_currency)
.where(
(gl.docstatus == 1)
& (gl.company == company)
& (gl.party_type == party_type)
& (gl.party == party)
& (gl.is_cancelled == 0)
)
.limit(1)
.run()
)
return existing_gle_currency[0][0] if existing_gle_currency else None
@@ -957,3 +995,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts)
doc.save()
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)

View File

@@ -116,7 +116,7 @@ class ReceivablePayableReport(object):
# build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries:
# get the balance object for voucher_type
key = (ple.voucher_type, ple.voucher_no, ple.party)
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type,
@@ -183,7 +183,7 @@ class ReceivablePayableReport(object):
):
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
@@ -192,13 +192,13 @@ class ReceivablePayableReport(object):
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
key = (ple.against_voucher_type, return_against, ple.party)
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
# no invoice, this is an invoice / stand-alone payment / credit note
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
row.party_type = ple.party_type
return row

View File

@@ -1,6 +1,7 @@
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
@@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_usd_account(self):
name = "Debtors USD"
exists = frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
)
if exists:
self.debtors_usd = exists[0].name
else:
debtors = frappe.get_doc(
"Account",
frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
)[0].name,
)
debtors_usd = frappe.new_doc("Account")
debtors_usd.company = debtors.company
debtors_usd.account_name = "Debtors USD"
debtors_usd.account_currency = "USD"
debtors_usd.parent_account = debtors.parent_account
debtors_usd.account_type = debtors.account_type
self.debtors_usd = debtors_usd.save().name
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
si = create_sales_invoice(
@@ -643,3 +621,94 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(report[1]), 2)
output_for = set([x.party for x in report[1]])
self.assertEqual(output_for, expected_output)
def test_report_output_if_party_is_missing(self):
acc_name = "Additional Debtors"
if not frappe.db.get_value(
"Account", filters={"account_name": acc_name, "company": self.company}
):
additional_receivable_acc = frappe.get_doc(
{
"doctype": "Account",
"account_name": acc_name,
"parent_account": "Accounts Receivable - " + self.company_abbr,
"company": self.company,
"account_type": "Receivable",
}
).save()
self.debtors2 = additional_receivable_acc.name
je = frappe.new_doc("Journal Entry")
je.company = self.company
je.posting_date = today()
je.append(
"accounts",
{
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 150,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.debtors2,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 200,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.cash,
"debit_in_account_currency": 0,
"credit_in_account_currency": 350,
"cost_center": self.cost_center,
},
)
je.save().submit()
# manually remove party from Payment Ledger
ple = qb.DocType("Payment Ledger Entry")
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
filters = {
"company": self.company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report_ouput = execute(filters)[1]
expected_data = [
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
]
self.assertEqual(len(report_ouput), 2)
# fetch only required fields
report_output = [
[
x.party_account,
x.voucher_type,
x.voucher_no,
"Customer",
self.customer,
x.invoiced,
x.paid,
x.credit_note,
x.outstanding,
]
for x in report_ouput
]
# use account name to sort
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
report_output = sorted(report_output, key=lambda x: x[0])
self.assertEqual(expected_data, report_output)

View File

@@ -23,6 +23,7 @@ class TestBankReconciliationStatement(FrappeTestCase):
"Payment Entry",
]:
frappe.db.delete(dt)
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def test_loan_entries_in_bank_reco_statement(self):
create_loan_accounts()

View File

@@ -67,7 +67,7 @@ def setup_mappers(mappers):
mapping["finance_costs"] = []
mapping["finance_costs_adjustments"] = []
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
mapping_names = [item.name for item in doc.accounts]
mapping_names = [item.mapping for item in doc.accounts]
if not mapping_names:
continue

View File

@@ -133,15 +133,17 @@ class General_Payment_Ledger_Comparison(object):
self.gle_balances = set(val.gle) | self.gle_balances
self.ple_balances = set(val.ple) | self.ple_balances
self.diff1 = self.gle_balances.difference(self.ple_balances)
self.diff2 = self.ple_balances.difference(self.gle_balances)
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
self.diff = frappe._dict({})
for x in self.diff1:
for x in self.variation_in_payment_ledger:
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
for x in self.diff2:
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
for x in self.variation_in_general_ledger:
self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update(
frappe._dict({"pl_balance": x[4]})
)
def generate_data(self):
self.data = []

View File

@@ -544,6 +544,8 @@ class GrossProfitGenerator(object):
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)

View File

@@ -33,13 +33,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Accounting Dimension"),
"fieldtype": "Link",
"options": "Accounting Dimension",
"get_query": () =>{
return {
filters: {
"disabled": 0
}
}
}
},
{
"fieldname": "fiscal_year",

View File

@@ -68,7 +68,11 @@ def get_result(
tax_amount += entry.credit - entry.debit
if net_total_map.get(name):
total_amount, grand_total, base_total = net_total_map.get(name)
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
total_amount = grand_total = base_total = tax_amount / (rate / 100)
else:
total_amount, grand_total, base_total = net_total_map.get(name)
else:
total_amount += entry.credit

View File

@@ -624,7 +624,7 @@ def update_reference_in_payment_entry(
"outstanding_amount": d.outstanding_amount,
"allocated_amount": d.allocated_amount,
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
"exchange_gain_loss": d.exchange_gain_loss,
}
if d.voucher_detail_no:
@@ -636,28 +636,29 @@ def update_reference_in_payment_entry(
existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid()
original_row = existing_row.as_dict().copy()
existing_row.update(reference_details)
if d.allocated_amount <= existing_row.allocated_amount:
existing_row.allocated_amount -= d.allocated_amount
if d.allocated_amount < original_row.allocated_amount:
new_row = payment_entry.append("references")
new_row.docstatus = 1
for field in list(reference_details):
new_row.set(field, original_row[field])
new_row.set(field, reference_details[field])
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
new_row.update(reference_details)
payment_entry.flags.ignore_validate_update_after_submit = True
payment_entry.clear_unallocated_reference_document_rows()
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details()
payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal()
payment_entry.make_exchange_gain_loss_journal(
frappe._dict({"difference_posting_date": d.difference_posting_date})
)
if not do_not_save:
payment_entry.save(ignore_permissions=True)

View File

@@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
frm.set_query("item_code", function() {
return {
"filters": {
"disabled": 0,
"is_fixed_asset": 1,
"is_stock_item": 0
}
@@ -284,7 +283,7 @@ frappe.ui.form.on('Asset', {
item_code: function(frm) {
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger('set_finance_book');
} else {
frm.set_value('finance_books', []);
@@ -448,7 +447,7 @@ frappe.ui.form.on('Asset', {
calculate_depreciation: function(frm) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger("set_finance_book");
} else {
frm.set_value("finance_books", []);

View File

@@ -221,11 +221,11 @@
"read_only": 1
},
{
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
"options": "Company:company:default_currency",
"read_only": 1,
"read_only_depends_on": "eval:!doc.is_existing_asset",
"reqd": 1
},
@@ -413,6 +413,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_receipt",
"fieldtype": "Link",
"label": "Purchase Receipt",
@@ -430,6 +431,7 @@
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_invoice",
"fieldtype": "Link",
"label": "Purchase Invoice",
@@ -493,10 +495,11 @@
"read_only": 1
},
{
"depends_on": "eval.doc.asset_quantity",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
"read_only": 1
},
{
"fieldname": "depr_entry_posting_status",
@@ -555,9 +558,14 @@
"link_doctype": "Journal Entry",
"link_fieldname": "reference_name",
"table_fieldname": "accounts"
},
{
"group": "Asset Capitalization",
"link_doctype": "Asset Capitalization",
"link_fieldname": "target_asset"
}
],
"modified": "2023-10-03 23:28:26.732269",
"modified": "2023-10-27 17:03:46.629617",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -186,6 +186,7 @@ class TestAsset(AssetSetup):
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset=1)
doc = frappe.new_doc("Purchase Invoice")
doc.company = "_Test Company"
doc.supplier = "_Test Supplier"
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
@@ -487,7 +488,7 @@ class TestAsset(AssetSetup):
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
# CWIP: Capital Work In Progress
# Capital Work In Progress
def test_cwip_accounting(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
@@ -520,7 +521,8 @@ class TestAsset(AssetSetup):
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
("CWIP Account - _TC", 5250.0, 0.0),
)
@@ -539,9 +541,8 @@ class TestAsset(AssetSetup):
expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
pi_gle = frappe.db.sql(
@@ -1731,6 +1732,7 @@ def create_asset_category():
"fixed_asset_account": "_Test Fixed Asset - _TC",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC",
"capital_work_in_progress_account": "CWIP Account - _TC",
},
)
asset_category.append(

View File

@@ -832,12 +832,8 @@ def get_items_tagged_to_wip_composite_asset(asset):
"amount",
]
pi_items = frappe.get_all(
"Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields
)
pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
)
return pi_items + pr_items
return pr_items

View File

@@ -16,7 +16,7 @@
"transaction_settings_section",
"po_required",
"pr_required",
"over_order_allowance",
"blanket_order_allowance",
"column_break_12",
"maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
@@ -24,6 +24,7 @@
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
"show_pay_button",
"use_transaction_date_exchange_rate",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -160,10 +161,17 @@
},
{
"default": "0",
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
"fieldname": "over_order_allowance",
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate"
},
{
"default": "0",
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
"label": "Blanket Order Allowance (%)"
}
],
"icon": "fa fa-cog",
@@ -171,7 +179,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-03-02 17:02:14.404622",
"modified": "2023-10-25 14:03:32.520418",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -522,6 +522,9 @@ def make_purchase_receipt(source_name, target_doc=None):
"bom": "bom",
"material_request": "material_request",
"material_request_item": "material_request_item",
"sales_order": "sales_order",
"sales_order_item": "sales_order_item",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
@@ -598,6 +601,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"field_map": {
"name": "po_detail",
"parent": "purchase_order",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),

View File

@@ -86,6 +86,8 @@
"billed_amt",
"accounting_details",
"expense_account",
"column_break_fyqr",
"wip_composite_asset",
"manufacture_details",
"manufacturer",
"manufacturer_part_no",
@@ -897,13 +899,23 @@
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
},
{
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "column_break_fyqr",
"fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-09-13 16:22:40.825092",
"modified": "2023-10-27 15:50:42.655573",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -8,7 +8,7 @@ def get_data():
"This is based on transactions against this Supplier. See timeline below for details"
),
"fieldname": "supplier",
"non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"},
"non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"},
"transactions": [
{"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
{"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},

View File

@@ -6,7 +6,7 @@ import copy
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import date_diff, flt, getdate
@@ -57,7 +57,7 @@ def get_data(filters):
po_item.qty,
po_item.received_qty,
(po_item.qty - po_item.received_qty).as_("pending_qty"),
IfNull(pi_item.qty, 0).as_("billed_qty"),
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
po_item.base_amount.as_("amount"),
(po_item.received_qty * po_item.base_rate).as_("received_qty_amount"),
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),

View File

@@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
}
}
}
else {
return {
filters: { "disabled": 0 }
}
}
}
},
{

View File

@@ -12,6 +12,7 @@ from frappe.utils import (
add_days,
add_months,
cint,
comma_and,
flt,
fmt_money,
formatdate,
@@ -180,6 +181,17 @@ class AccountsController(TransactionBase):
self.validate_party_account_currency()
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
if invalid_advances := [
x for x in self.advances if not x.reference_type or not x.reference_name
]:
frappe.throw(
_(
"Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
).format(
frappe.bold(comma_and([x.idx for x in invalid_advances])), frappe.bold(_("Advance Payments"))
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -571,6 +583,17 @@ class AccountsController(TransactionBase):
self.currency, self.company_currency, transaction_date, args
)
if (
self.currency
and buying_or_selling == "Buying"
and frappe.db.get_single_value("Buying Settings", "use_transaction_date_exchange_rate")
and self.doctype == "Purchase Invoice"
):
self.use_transaction_date_exchange_rate = True
self.conversion_rate = get_exchange_rate(
self.currency, self.company_currency, transaction_date, args
)
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -594,6 +617,7 @@ class AccountsController(TransactionBase):
self.pricing_rules = []
selected_serial_nos_map = {}
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@@ -605,6 +629,7 @@ class AccountsController(TransactionBase):
args["ignore_pricing_rule"] = (
self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
)
args["ignore_serial_nos"] = selected_serial_nos_map.get(item.get("item_code"))
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
@@ -661,6 +686,11 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if ret.get("serial_no"):
selected_serial_nos_map.setdefault(item.get("item_code"), []).extend(
ret.get("serial_no").split("\n")
)
else:
# Transactions line item without item code
@@ -1101,7 +1131,9 @@ class AccountsController(TransactionBase):
self.name,
arg.get("referenced_row"),
):
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
arg.voucher_type, arg.voucher_no, "posting_date"
)
je = create_gain_loss_journal(
self.company,
posting_date,
@@ -1184,7 +1216,7 @@ class AccountsController(TransactionBase):
je = create_gain_loss_journal(
self.company,
self.posting_date,
args.get("difference_posting_date") if args else self.posting_date,
self.party_type,
self.party,
party_account,

View File

@@ -4,9 +4,9 @@
import frappe
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController
@@ -591,7 +591,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []

View File

@@ -62,9 +62,12 @@ class StockController(AccountsController):
)
)
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
if (
cint(erpnext.is_perpetual_inventory_enabled(self.company))
or provisional_accounting_for_non_stock_items
or is_asset_pr
):
warehouse_account = get_warehouse_account_map(self.company)
@@ -73,11 +76,6 @@ class StockController(AccountsController):
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost)
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -670,13 +668,21 @@ class StockController(AccountsController):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self):
if (
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
and self.is_internal_transfer()
):
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
if self.is_internal_transfer():
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
else:
self.validate_internal_transfer_warehouse()
def validate_internal_transfer_warehouse(self):
for row in self.items:
if row.get("target_warehouse"):
row.target_warehouse = None
if row.get("from_warehouse"):
row.from_warehouse = None
def validate_in_transit_warehouses(self):
if (

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowdate
from frappe.utils import add_days, flt, getdate, nowdate
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -614,6 +614,73 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
def test_15_gain_loss_on_different_posting_date(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
)
# Payment
pe = (
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
.save()
.submit()
)
# There should be outstanding in both currencies
si.reload()
self.assertEqual(si.outstanding_amount, 2)
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
# Reconcile the remaining amount
pr = frappe.get_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Customer"
pr.party = self.customer
pr.receivable_payable_account = self.debit_usd
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
pr.reconcile()
# Exchange Gain/Loss Journal should've been created.
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(len(exc_je_for_pe), 1)
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
self.assertEqual(
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
getdate(add_days(nowdate(), 1)),
)
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
# There should be no outstanding
si.reload()
self.assertEqual(si.outstanding_amount, 0)
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
# Cancel Payment
pe.reload()
pe.cancel()
si.reload()
self.assertEqual(si.outstanding_amount, 2)
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)

View File

@@ -517,6 +517,7 @@ accounting_dimension_doctypes = [
"Sales Invoice Item",
"Purchase Invoice Item",
"Purchase Order Item",
"Sales Order Item",
"Journal Entry Account",
"Material Request Item",
"Delivery Note Item",

View File

@@ -411,11 +411,6 @@ def close_unsecured_term_loan(loan):
frappe.throw(_("Cannot close this loan until full repayment"))
def close_loan(loan, total_amount_paid):
frappe.db.set_value("Loan", loan, "total_amount_paid", total_amount_paid)
frappe.db.set_value("Loan", loan, "status", "Closed")
@frappe.whitelist()
def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0):
disbursement_entry = frappe.new_doc("Loan Disbursement")

View File

@@ -9,6 +9,9 @@ from frappe.utils import cint, flt, getdate
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
get_pending_principal_amount,
)
class LoanWriteOff(AccountsController):
@@ -39,11 +42,13 @@ class LoanWriteOff(AccountsController):
def on_submit(self):
self.update_outstanding_amount()
self.make_gl_entries()
self.close_employee_loan()
def on_cancel(self):
self.update_outstanding_amount(cancel=1)
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
self.make_gl_entries(cancel=1)
self.close_employee_loan(cancel=1)
def update_outstanding_amount(self, cancel=0):
written_off_amount = frappe.db.get_value("Loan", self.loan, "written_off_amount")
@@ -94,3 +99,39 @@ class LoanWriteOff(AccountsController):
)
make_gl_entries(gl_entries, cancel=cancel, merge_entries=False)
def close_employee_loan(self, cancel=0):
if not frappe.db.has_column("Loan", "repay_from_salary"):
return
loan = frappe.get_value(
"Loan",
self.loan,
[
"total_payment",
"total_principal_paid",
"loan_amount",
"total_interest_payable",
"written_off_amount",
"disbursed_amount",
"status",
"is_secured_loan",
"repay_from_salary",
"name",
],
as_dict=1,
)
if loan.is_secured_loan or not loan.repay_from_salary:
return
if not cancel:
pending_principal_amount = get_pending_principal_amount(loan)
precision = cint(frappe.db.get_default("currency_precision")) or 2
if flt(pending_principal_amount, precision) <= 0:
frappe.db.set_value("Loan", loan.name, "status", "Closed")
frappe.msgprint(_("Loan {0} closed").format(loan.name))
else:
frappe.db.set_value("Loan", loan.loan, "status", "Disbursed")

View File

@@ -107,7 +107,7 @@ def validate_against_blanket_order(order_doc):
allowance = flt(
frappe.db.get_single_value(
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
"over_order_allowance",
"blanket_order_allowance",
)
)
for bo_name, item_data in order_data.items():

View File

@@ -63,7 +63,7 @@ class TestBlanketOrder(FrappeTestCase):
po1.currency = get_company_currency(po1.company)
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def test_over_order_allowance(self):
def test_blanket_order_allowance(self):
# Sales Order
bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
@@ -74,7 +74,7 @@ class TestBlanketOrder(FrappeTestCase):
so.items[0].qty = 110
self.assertRaises(frappe.ValidationError, so.submit)
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 10)
so.submit()
# Purchase Order
@@ -87,7 +87,7 @@ class TestBlanketOrder(FrappeTestCase):
po.items[0].qty = 110
self.assertRaises(frappe.ValidationError, po.submit)
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
po.submit()

View File

@@ -1172,12 +1172,12 @@ def get_children(parent=None, is_root=False, **filters):
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []
expenses_included_in_valuation = frappe.get_cached_value(
"Company", work_order.company, "expenses_included_in_valuation"
default_expense_account = frappe.get_cached_value(
"Company", work_order.company, "default_expense_account"
)
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
add_non_stock_items_cost(stock_entry, work_order, default_expense_account)
add_operations_cost(stock_entry, work_order, default_expense_account)
def add_non_stock_items_cost(stock_entry, work_order, expense_account):

View File

@@ -10,8 +10,8 @@ frappe.views.calendar["Job Card"] = {
},
gantt: {
field_map: {
"start": "started_time",
"end": "started_time",
"start": "expected_start_date",
"end": "expected_end_date",
"id": "name",
"title": "subject",
"color": "color",

View File

@@ -1,6 +1,6 @@
frappe.listview_settings['Job Card'] = {
has_indicator_for_draft: true,
add_fields: ["expected_start_date", "expected_end_date"],
get_indicator: function(doc) {
const status_colors = {
"Work In Progress": "orange",

View File

@@ -1734,7 +1734,10 @@ def get_raw_materials_of_sub_assembly_items(
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
item_details.setdefault(item.get("item_code"), item)
if details := item_details.get(item.get("item_code")):
details.qty += item.get("qty")
else:
item_details.setdefault(item.get("item_code"), item)
return item_details

View File

@@ -1321,6 +1321,33 @@ class TestProductionPlan(FrappeTestCase):
self.assertTrue(row.warehouse == mrp_warhouse)
self.assertEqual(row.quantity, 12)
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree = {
"Fininshed Goods2 For SUB Test": {
"SubAssembly2 For SUB Test": {"ChildPart2 For SUB Test": {}},
"SubAssembly3 For SUB Test": {"ChildPart2 For SUB Test": {}},
}
}
parent_bom = create_nested_bom(bom_tree, prefix="")
plan = create_production_plan(
item_code=parent_bom.item,
planned_qty=1,
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
warehouse="_Test Warehouse - _TC",
)
plan.get_sub_assembly_items()
plan.make_material_request()
for row in plan.mr_items:
if row.item_code == "ChildPart2 For SUB Test":
self.assertEqual(row.quantity, 2)
def create_production_plan(**args):
"""

View File

@@ -12,7 +12,7 @@ frappe.query_reports["BOM Operations Time"] = {
"options": "Item",
"get_query": () =>{
return {
filters: { "disabled": 0, "is_stock_item": 1 }
filters: { "is_stock_item": 1 }
}
}
},

View File

@@ -341,5 +341,8 @@ execute:frappe.defaults.clear_default("fiscal_year")
execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow
erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item
erpnext.patches.v14_0.rename_over_order_allowance_field
erpnext.patches.v14_0.migrate_delivery_stop_lock_field
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger

View File

@@ -1,42 +0,0 @@
import frappe
def execute():
frappe.reload_doctype("Landed Cost Taxes and Charges")
company_account_map = frappe._dict(
frappe.db.sql(
"""
SELECT name, expenses_included_in_valuation from `tabCompany`
"""
)
)
for company, account in company_account_map.items():
frappe.db.sql(
"""
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l
SET
t.expense_account = %s
WHERE
l.docstatus = 1
AND l.company = %s
AND t.parent = l.name
""",
(account, company),
)
frappe.db.sql(
"""
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabStock Entry` s
SET
t.expense_account = %s
WHERE
s.docstatus = 1
AND s.company = %s
AND t.parent = s.name
""",
(account, company),
)

View File

@@ -0,0 +1,7 @@
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
create_accounting_dimensions_for_doctype,
)
def execute():
create_accounting_dimensions_for_doctype(doctype="Sales Order Item")

View File

@@ -0,0 +1,7 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
if frappe.db.has_column("Delivery Stop", "lock"):
rename_field("Delivery Stop", "lock", "locked")

View File

@@ -0,0 +1,15 @@
from frappe.model.utils.rename_field import rename_field
def execute():
rename_field(
"Buying Settings",
"over_order_allowance",
"blanket_order_allowance",
)
rename_field(
"Selling Settings",
"over_order_allowance",
"blanket_order_allowance",
)

View File

@@ -24,6 +24,7 @@
},
{
"fetch_from": "task.subject",
"fetch_if_empty": 1,
"fieldname": "subject",
"fieldtype": "Text",
"in_list_view": 1,
@@ -31,7 +32,6 @@
"read_only": 1
},
{
"fetch_from": "task.project",
"fieldname": "project",
"fieldtype": "Text",
"label": "Project",
@@ -40,7 +40,7 @@
],
"istable": 1,
"links": [],
"modified": "2023-10-09 11:34:14.335853",
"modified": "2023-10-17 12:45:21.536165",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task Depends On",

View File

@@ -28,7 +28,6 @@ frappe.ui.form.on(cur_frm.doctype, {
filters: {
"account_type": account_type,
"company": doc.company,
"disabled": 0
}
}
});

View File

@@ -17,9 +17,9 @@ erpnext.accounts.unreconcile_payments = {
},
callback: function(r) {
if (r.message) {
frm.add_custom_button(__("Un-Reconcile"), function() {
frm.add_custom_button(__("UnReconcile"), function() {
erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
});
}, __('Actions'));
}
}
});
@@ -87,11 +87,11 @@ erpnext.accounts.unreconcile_payments = {
unreconcile_dialog_fields[0].get_data = function(){ return r.message};
let d = new frappe.ui.Dialog({
title: 'Un-Reconcile Allocations',
title: 'UnReconcile Allocations',
fields: unreconcile_dialog_fields,
size: 'large',
cannot_add_rows: true,
primary_action_label: 'Un-Reconcile',
primary_action_label: 'UnReconcile',
primary_action(values) {
let selected_allocations = values.allocations.filter(x=>x.__checked);

View File

@@ -177,7 +177,8 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
"parent": invoice.name,
"item_tax_template": vat_setting.item_tax_template,
},
fields=["item_code", "base_net_amount"],
fields=["item_code", "sum(base_net_amount) as base_net_amount"],
group_by="item_code, item_tax_template",
)
for item in invoice_items:

View File

@@ -1631,7 +1631,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-04-20 11:14:01.036202",
"modified": "2023-10-18 12:41:54.813462",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -542,29 +542,37 @@ def close_or_unclose_sales_orders(names, status):
def get_requested_item_qty(sales_order):
return frappe._dict(
frappe.db.sql(
"""
select sales_order_item, sum(qty)
from `tabMaterial Request Item`
where docstatus = 1
and sales_order = %s
group by sales_order_item
""",
sales_order,
)
)
result = {}
for d in frappe.db.get_all(
"Material Request Item",
filters={"docstatus": 1, "sales_order": sales_order},
fields=["sales_order_item", "sum(qty) as qty", "sum(received_qty) as received_qty"],
group_by="sales_order_item",
):
result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty})
return result
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name)
def get_remaining_qty(so_item):
return flt(
flt(so_item.qty)
- flt(requested_item_qty.get(so_item.name, {}).get("qty"))
- max(
flt(so_item.get("delivered_qty"))
- flt(requested_item_qty.get(so_item.name, {}).get("received_qty")),
0,
)
)
def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field
qty = source.get("qty")
target.project = source_parent.project
target.qty = qty - requested_item_qty.get(source.name, 0) - flt(source.get("delivered_qty"))
target.qty = get_remaining_qty(source)
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
args = target.as_dict().copy()
@@ -597,8 +605,8 @@ def make_material_request(source_name, target_doc=None):
"Sales Order Item": {
"doctype": "Material Request Item",
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
"condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code)
and (doc.stock_qty - flt(doc.get("delivered_qty"))) > requested_item_qty.get(doc.name, 0),
"condition": lambda item: not frappe.db.exists("Product Bundle", item.item_code)
and get_remaining_qty(item) > 0,
"postprocess": update_item,
},
},

View File

@@ -1893,10 +1893,10 @@ class TestSalesOrder(FrappeTestCase):
si.submit()
pe.load_from_db()
self.assertEqual(pe.references[0].reference_name, si.name)
self.assertEqual(pe.references[0].allocated_amount, 200)
self.assertEqual(pe.references[1].reference_name, so.name)
self.assertEqual(pe.references[1].allocated_amount, 300)
self.assertEqual(pe.references[0].reference_name, so.name)
self.assertEqual(pe.references[0].allocated_amount, 300)
self.assertEqual(pe.references[1].reference_name, si.name)
self.assertEqual(pe.references[1].allocated_amount, 200)
def test_delivered_item_material_request(self):
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."

View File

@@ -66,6 +66,7 @@
"total_weight",
"column_break_21",
"weight_uom",
"accounting_dimensions_section",
"warehouse_and_reference",
"warehouse",
"target_warehouse",
@@ -868,12 +869,18 @@
"label": "Production Plan Qty",
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-07-28 14:56:42.031636",
"modified": "2023-10-17 18:18:26.475259",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
@@ -884,4 +891,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -25,7 +25,7 @@
"so_required",
"dn_required",
"sales_update_frequency",
"over_order_allowance",
"blanket_order_allowance",
"column_break_5",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
@@ -182,17 +182,17 @@
"fieldtype": "Check",
"label": "Allow Sales Order Creation For Expired Quotation"
},
{
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
"fieldname": "over_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
},
{
"default": "0",
"fieldname": "allow_negative_rates_for_items",
"fieldtype": "Check",
"label": "Allow Negative rates for Items"
},
{
"description": "Percentage you are allowed to sell beyond the Blanket Order quantity.",
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Blanket Order Allowance (%)"
}
],
"icon": "fa fa-cog",
@@ -200,7 +200,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-08-14 20:33:05.693667",
"modified": "2023-10-25 14:03:03.966701",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@@ -223,7 +223,6 @@ erpnext.company.setup_queries = function(frm) {
["cost_center", {}],
["round_off_cost_center", {}],
["depreciation_cost_center", {}],
["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"]]}],
@@ -236,8 +235,6 @@ erpnext.company.setup_queries = function(frm) {
$.each([
["stock_adjustment_account",
{"root_type": "Expense", "account_type": "Stock Adjustment"}],
["expenses_included_in_valuation",
{"root_type": "Expense", "account_type": "Expenses Included in Valuation"}],
["stock_received_but_not_billed",
{"root_type": "Liability", "account_type": "Stock Received But Not Billed"}],
["service_received_but_not_billed",

View File

@@ -79,12 +79,10 @@
"column_break_32",
"stock_received_but_not_billed",
"default_provisional_account",
"expenses_included_in_valuation",
"fixed_asset_defaults",
"accumulated_depreciation_account",
"depreciation_expense_account",
"series_for_depreciation_entry",
"expenses_included_in_asset_valuation",
"column_break_40",
"disposal_account",
"depreciation_cost_center",
@@ -466,14 +464,6 @@
"no_copy": 1,
"options": "Account"
},
{
"fieldname": "expenses_included_in_valuation",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Expenses Included In Valuation",
"no_copy": 1,
"options": "Account"
},
{
"fieldname": "accumulated_depreciation_account",
"fieldtype": "Link",
@@ -493,12 +483,6 @@
"fieldtype": "Data",
"label": "Series for Asset Depreciation Entry (Journal Entry)"
},
{
"fieldname": "expenses_included_in_asset_valuation",
"fieldtype": "Link",
"label": "Expenses Included In Asset Valuation",
"options": "Account"
},
{
"fieldname": "column_break_40",
"fieldtype": "Column Break"
@@ -728,7 +712,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2023-07-07 05:41:41.537256",
"modified": "2023-10-23 10:19:24.322898",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -92,7 +92,6 @@ class Company(NestedSet):
["Default Income Account", "default_income_account"],
["Stock Received But Not Billed Account", "stock_received_but_not_billed"],
["Stock Adjustment Account", "stock_adjustment_account"],
["Expense Included In Valuation Account", "expenses_included_in_valuation"],
]
for account in accounts:
@@ -384,7 +383,6 @@ class Company(NestedSet):
"depreciation_expense_account": "Depreciation",
"capital_work_in_progress_account": "Capital Work in Progress",
"asset_received_but_not_billed": "Asset Received But Not Billed",
"expenses_included_in_asset_valuation": "Expenses Included In Asset Valuation",
"default_expense_account": "Cost of Goods Sold",
}
@@ -394,7 +392,6 @@ class Company(NestedSet):
"stock_received_but_not_billed": "Stock Received But Not Billed",
"default_inventory_account": "Stock",
"stock_adjustment_account": "Stock Adjustment",
"expenses_included_in_valuation": "Expenses Included In Valuation",
}
)

View File

@@ -33,6 +33,7 @@ def after_install():
add_app_name()
setup_log_settings()
hide_workspaces()
update_roles()
frappe.db.commit()
@@ -214,6 +215,12 @@ def hide_workspaces():
frappe.db.set_value("Workspace", ws, "public", 0)
def update_roles():
website_user_roles = ("Customer", "Supplier")
for role in website_user_roles:
frappe.db.set_value("Role", role, "desk_access", 0)
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
role_profile = frappe.new_doc("Role Profile")

View File

@@ -144,6 +144,7 @@ class DeliveryNote(SellingController):
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
self.set_product_bundle_reference_in_packed_items() # should be called before `make_packing_list`
make_packing_list(self)
if self._action != "submit" and not self.is_return:
@@ -430,6 +431,17 @@ class DeliveryNote(SellingController):
else:
serial_nos.append(serial_no)
def set_product_bundle_reference_in_packed_items(self):
if self.packed_items and ((self.is_return and self.return_against) or self.amended_from):
if items_ref_map := {
item.dn_detail or item.get("_amended_from"): item.name
for item in self.items
if item.dn_detail or item.get("_amended_from")
}:
for item in self.packed_items:
if item.parent_detail_docname in items_ref_map:
item.parent_detail_docname = items_ref_map[item.parent_detail_docname]
def update_billed_amount_based_on_so(so_detail, update_modified=True):
from frappe.query_builder.functions import Sum

View File

@@ -5,11 +5,12 @@
import json
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.utils import get_balance_on
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
@@ -268,8 +269,6 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.items[0].returned_qty, 2)
self.assertEqual(dn.per_returned, 40)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn_2 = make_return_doc("Delivery Note", dn.name)
# Check if unreturned amount is mapped in 2nd return
@@ -361,8 +360,6 @@ class TestDeliveryNote(FrappeTestCase):
dn.submit()
self.assertEqual(dn.items[0].incoming_rate, 150)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.items[0].warehouse = return_warehouse
return_dn.save().submit()
@@ -1182,7 +1179,6 @@ class TestDeliveryNote(FrappeTestCase):
)
def test_batch_expiry_for_delivery_note(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
item = make_item(
@@ -1239,10 +1235,101 @@ class TestDeliveryNote(FrappeTestCase):
# Test - 1: ValidationError should be raised
self.assertRaises(frappe.ValidationError, dn.submit)
def test_packed_items_for_return_delivery_note(self):
# Step - 1: Create Items
product_bundle_item = make_item(properties={"is_stock_item": 0}).name
batch_item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TEST-BATCH-.#####",
}
).name
serial_item = make_item(
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TEST-SERIAL-.#####"}
).name
# Step - 2: Inward Stock
se1 = make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
serial_nos = (
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3)
.items[0]
.serial_no
)
# Step - 3: Create a Product Bundle
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import (
create_product_bundle_item,
)
create_product_bundle_item(product_bundle_item, packed_items=[[batch_item, 1], [serial_item, 1]])
# Step - 4: Create a Delivery Note for the Product Bundle
dn = create_delivery_note(
item_code=product_bundle_item,
warehouse="_Test Warehouse - _TC",
qty=3,
do_not_submit=True,
)
dn.packed_items[1].serial_no = serial_nos
dn.save()
dn.submit()
# Step - 5: Create a Return Delivery Note(Sales Return)
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.save()
return_dn.submit()
self.assertEqual(return_dn.packed_items[0].batch_no, dn.packed_items[0].batch_no)
self.assertEqual(return_dn.packed_items[1].serial_no, dn.packed_items[1].serial_no)
@change_settings("Stock Settings", {"automatically_set_serial_nos_based_on_fifo": 1})
def test_delivery_note_for_repetitive_serial_item(self):
# Step - 1: Create Serial Item
item, warehouse = (
make_item(
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TEST-SERIAL-.###"}
).name,
"_Test Warehouse - _TC",
)
# Step - 2: Inward Stock
make_stock_entry(item_code=item, target=warehouse, qty=5)
# Step - 3: Create Delivery Note with repetitive Serial Item
dn = create_delivery_note(item_code=item, warehouse=warehouse, qty=2, do_not_save=True)
dn.append("items", dn.items[0].as_dict())
dn.items[1].qty = 3
dn.save()
dn.submit()
# Test - 1: Serial Nos should be different for each line item
serial_nos = []
for item in dn.items:
for serial_no in item.serial_no.split("\n"):
self.assertNotIn(serial_no, serial_nos)
serial_nos.append(serial_no)
def tearDown(self):
frappe.db.rollback()
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
def test_non_internal_transfer_delivery_note(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
dn = create_delivery_note(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", company=dn.company)
dn.items[0].db_set("target_warehouse", warehouse)
dn.reload()
self.assertEqual(dn.items[0].target_warehouse, warehouse)
dn.save()
dn.reload()
self.assertFalse(dn.items[0].target_warehouse)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -741,7 +741,8 @@
"label": "Against Delivery Note Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "stock_qty_sec_break",
@@ -868,7 +869,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-07-25 11:58:28.101919",
"modified": "2023-10-16 16:18:18.013379",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@@ -1,815 +1,197 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-10-16 16:46:28.166950",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"creation": "2017-10-16 16:46:28.166950",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer",
"address",
"locked",
"column_break_6",
"customer_address",
"visited",
"order_information_section",
"delivery_note",
"cb_order",
"grand_total",
"section_break_7",
"contact",
"email_sent_to",
"column_break_7",
"customer_contact",
"section_break_9",
"distance",
"estimated_arrival",
"lat",
"column_break_19",
"uom",
"lng",
"more_information_section",
"details"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "customer",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Customer",
"length": 0,
"no_copy": 0,
"options": "Customer",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"columns": 2,
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Address Name",
"length": 0,
"no_copy": 0,
"options": "Address",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "address",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Address Name",
"options": "Address",
"print_hide": 1,
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lock",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Lock",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "locked",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Locked"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "customer_address",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Customer Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "customer_address",
"fieldtype": "Small Text",
"label": "Customer Address",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus==1",
"fieldname": "visited",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Visited",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"allow_on_submit": 1,
"default": "0",
"depends_on": "eval:doc.docstatus==1",
"fieldname": "visited",
"fieldtype": "Check",
"label": "Visited",
"no_copy": 1,
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "order_information_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Order Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "order_information_section",
"fieldtype": "Section Break",
"label": "Order Information"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "delivery_note",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Delivery Note",
"length": 0,
"no_copy": 1,
"options": "Delivery Note",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "delivery_note",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Delivery Note",
"no_copy": 1,
"options": "Delivery Note",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_order",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "cb_order",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grand_total",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Grand Total",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "grand_total",
"fieldtype": "Currency",
"label": "Grand Total",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Contact Information"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact Name",
"length": 0,
"no_copy": 0,
"options": "Contact",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "contact",
"fieldtype": "Link",
"label": "Contact Name",
"options": "Contact",
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_sent_to",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email sent to",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "email_sent_to",
"fieldtype": "Data",
"label": "Email sent to",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "customer_contact",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Customer Contact",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "customer_contact",
"fieldtype": "Small Text",
"label": "Customer Contact",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dispatch Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"label": "Dispatch Information"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "distance",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Distance",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "2",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "distance",
"fieldtype": "Float",
"label": "Distance",
"precision": "2",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "estimated_arrival",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Estimated Arrival",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "estimated_arrival",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Estimated Arrival"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lat",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Latitude",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "lat",
"fieldtype": "Float",
"hidden": 1,
"label": "Latitude"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_19",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "eval:doc.distance",
"fieldname": "uom",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "UOM",
"length": 0,
"no_copy": 0,
"options": "UOM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"depends_on": "eval:doc.distance",
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lng",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Longitude",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "lng",
"fieldtype": "Float",
"hidden": 1,
"label": "Longitude"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "more_information_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "More Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "more_information_section",
"fieldtype": "Section Break",
"label": "More Information"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "details",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "details",
"fieldtype": "Text Editor",
"label": "Details"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-10-16 05:23:25.661542",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Stop",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"istable": 1,
"links": [],
"modified": "2023-09-29 09:22:53.435161",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Stop",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -62,8 +62,13 @@ frappe.ui.form.on('Delivery Trip', {
company: frm.doc.company,
}
})
}, __("Get customers from"));
}, __("Get stops from"));
}
frm.add_custom_button(__("Delivery Notes"), function () {
frappe.set_route("List", "Delivery Note",
{'name': ["in", frm.doc.delivery_stops.map((stop) => {return stop.delivery_note;})]}
);
}, __("View"));
},
calculate_arrival_time: function (frm) {

View File

@@ -170,7 +170,7 @@ class DeliveryTrip(Document):
for stop in self.delivery_stops:
leg.append(stop.customer_address)
if optimize and stop.lock:
if optimize and stop.locked:
route_list.append(leg)
leg = [stop.customer_address]

View File

@@ -46,7 +46,7 @@ class TestDeliveryTrip(FrappeTestCase):
self.assertEqual(len(route_list[0]), 4)
def test_unoptimized_route_list_with_locks(self):
self.delivery_trip.delivery_stops[0].lock = 1
self.delivery_trip.delivery_stops[0].locked = 1
self.delivery_trip.save()
route_list = self.delivery_trip.form_route_list(optimize=False)
@@ -65,7 +65,7 @@ class TestDeliveryTrip(FrappeTestCase):
self.assertEqual(len(route_list[0]), 4)
def test_optimized_route_list_with_locks(self):
self.delivery_trip.delivery_stops[0].lock = 1
self.delivery_trip.delivery_stops[0].locked = 1
self.delivery_trip.save()
route_list = self.delivery_trip.form_route_list(optimize=True)

View File

@@ -280,7 +280,7 @@ class Item(Document):
# add item taxes from template
for d in template.get("taxes"):
self.append("taxes", {"item_tax_template": d.item_tax_template})
self.append("taxes", d)
# copy re-order table if empty
if not self.get("reorder_levels"):

View File

@@ -893,6 +893,8 @@ def create_item(
opening_stock=0,
is_fixed_asset=0,
asset_category=None,
buying_cost_center=None,
selling_cost_center=None,
company="_Test Company",
):
if not frappe.db.exists("Item", item_code):
@@ -910,7 +912,15 @@ def create_item(
item.is_purchase_item = is_purchase_item
item.is_customer_provided_item = is_customer_provided_item
item.customer = customer or ""
item.append("item_defaults", {"default_warehouse": warehouse, "company": company})
item.append(
"item_defaults",
{
"default_warehouse": warehouse,
"company": company,
"selling_cost_center": selling_cost_center,
"buying_cost_center": buying_cost_center,
},
)
item.save()
else:
item = frappe.get_doc("Item", item_code)

View File

@@ -6,7 +6,6 @@ frappe.ui.form.on("Item Price", {
frm.set_query("item_code", function() {
return {
filters: {
"disabled": 0,
"has_variants": 0
}
};

View File

@@ -405,6 +405,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
["uom", "uom"],
["sales_order", "sales_order"],
["sales_order_item", "sales_order_item"],
["wip_composite_asset", "wip_composite_asset"],
],
"postprocess": update_item,
"condition": select_item,

View File

@@ -37,6 +37,10 @@
"rate",
"col_break3",
"amount",
"accounting_details_section",
"expense_account",
"column_break_glru",
"wip_composite_asset",
"manufacture_details",
"manufacturer",
"manufacturer_part_no",
@@ -50,11 +54,10 @@
"lead_time_date",
"sales_order",
"sales_order_item",
"col_break4",
"production_plan",
"material_request_plan_item",
"job_card_item",
"col_break4",
"expense_account",
"section_break_46",
"page_break"
],
@@ -455,13 +458,28 @@
"label": "Job Card Item",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "column_break_glru",
"fieldtype": "Column Break"
},
{
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-05-07 20:23:31.250252",
"modified": "2023-10-27 15:53:41.444236",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",

View File

@@ -192,7 +192,6 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Parent Detail docname",
"no_copy": 1,
"oldfieldname": "parent_detail_docname",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -259,7 +258,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-04-28 13:16:38.460806",
"modified": "2023-10-14 23:26:11.755425",
"modified_by": "Administrator",
"module": "Stock",
"name": "Packed Item",

View File

@@ -13,7 +13,6 @@ from pypika import functions as fn
import erpnext
from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
@@ -146,8 +145,8 @@ class PurchaseReceipt(BuyingController):
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
# check cwip accounts before making auto assets
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
arbnb_account = self.get_company_default("asset_received_but_not_billed")
cwip_account = get_asset_account(
self.get_company_default("asset_received_but_not_billed")
get_asset_account(
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
)
break
@@ -315,7 +314,7 @@ class PurchaseReceipt(BuyingController):
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
self.make_tax_gl_entries(gl_entries)
self.get_asset_gl_entry(gl_entries)
update_regional_gl_entries(gl_entries, self)
return process_gl_map(gl_entries)
@@ -324,14 +323,6 @@ class PurchaseReceipt(BuyingController):
get_purchase_document_details,
)
stock_rbnb = None
if erpnext.is_perpetual_inventory_enabled(self.company):
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_with_no_account = []
stock_items = self.get_stock_items()
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
@@ -340,28 +331,258 @@ class PurchaseReceipt(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
def validate_account(account_type):
frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type))
def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name):
account_currency = get_account_currency(stock_asset_account_name)
if not stock_asset_account_name:
validate_account("Asset or warehouse account")
self.add_gl_entry(
gl_entries=gl_entries,
account=stock_asset_account_name,
cost_center=d.cost_center,
debit=stock_value_diff,
credit=0.0,
remarks=remarks,
against_account=stock_asset_rbnb,
account_currency=account_currency,
item=item,
)
def make_stock_received_but_not_billed_entry(item):
account = (
warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb
)
account_currency = get_account_currency(account)
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
credit_amount = (
flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency == self.company_currency
else flt(item.net_amount, item.precision("net_amount"))
)
outgoing_amount = item.base_net_amount
if self.is_internal_transfer() and item.valuation_rate:
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
credit_amount = outgoing_amount
if credit_amount:
if not account:
validate_account("Stock or Asset Received But Not Billed")
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=item.cost_center,
debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")),
credit=0.0,
remarks=remarks,
against_account=stock_asset_account_name,
debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")),
account_currency=account_currency,
item=item,
)
# check if the exchange rate has changed
if d.get("purchase_invoice"):
if (
exchange_rate_map[item.purchase_invoice]
and self.conversion_rate != exchange_rate_map[item.purchase_invoice]
and item.net_rate == net_rate_map[item.purchase_invoice_item]
):
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
exchange_rate_map[item.purchase_invoice] - self.conversion_rate
)
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=item.cost_center,
debit=0.0,
credit=discrepancy_caused_by_exchange_rate_difference,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=account_currency,
item=item,
)
self.add_gl_entry(
gl_entries=gl_entries,
account=self.get_company_default("exchange_gain_loss_account"),
cost_center=d.cost_center,
debit=discrepancy_caused_by_exchange_rate_difference,
credit=0.0,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=account_currency,
item=item,
)
return outgoing_amount
def make_landed_cost_gl_entries(item):
# Amount added through landed-cost-voucher
if item.landed_cost_voucher_amount and landed_cost_entries:
if (item.item_code, item.name) in landed_cost_entries:
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
account_currency = get_account_currency(account)
credit_amount = (
flt(amount["base_amount"])
if (amount["base_amount"] or account_currency != self.company_currency)
else flt(amount["amount"])
)
if not account:
validate_account("Landed Cost Account")
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=item.cost_center,
debit=0.0,
credit=credit_amount,
remarks=remarks,
against_account=stock_asset_account_name,
credit_in_account_currency=flt(amount["amount"]),
account_currency=account_currency,
project=item.project,
item=item,
)
def make_rate_difference_entry(item):
if item.rate_difference_with_purchase_invoice and stock_asset_rbnb:
account_currency = get_account_currency(stock_asset_rbnb)
self.add_gl_entry(
gl_entries=gl_entries,
account=stock_asset_rbnb,
cost_center=item.cost_center,
debit=0.0,
credit=flt(item.rate_difference_with_purchase_invoice),
remarks=_("Adjustment based on Purchase Invoice rate"),
against_account=stock_asset_account_name,
account_currency=account_currency,
project=item.project,
item=item,
)
def make_sub_contracting_gl_entries(item):
# sub-contracting warehouse
if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
self.add_gl_entry(
gl_entries=gl_entries,
account=supplier_warehouse_account,
cost_center=item.cost_center,
debit=0.0,
credit=flt(item.rm_supp_cost),
remarks=remarks,
against_account=stock_asset_account_name,
account_currency=supplier_warehouse_account_currency,
item=item,
)
def make_divisional_loss_gl_entry(item, outgoing_amount):
if item.is_fixed_asset:
return
# divisional loss adjustment
valuation_amount_as_per_doc = (
flt(outgoing_amount, d.precision("base_net_amount"))
+ flt(item.landed_cost_voucher_amount)
+ flt(item.rm_supp_cost)
+ flt(item.item_tax_amount)
+ flt(item.rate_difference_with_purchase_invoice)
)
divisional_loss = flt(
valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount")
)
if divisional_loss:
loss_account = (
self.get_company_default("default_expense_account", ignore_validation=True)
or stock_asset_rbnb
)
cost_center = item.cost_center or frappe.get_cached_value(
"Company", self.company, "cost_center"
)
account_currency = get_account_currency(loss_account)
self.add_gl_entry(
gl_entries=gl_entries,
account=loss_account,
cost_center=cost_center,
debit=divisional_loss,
credit=0.0,
remarks=remarks,
against_account=stock_asset_account_name,
account_currency=account_currency,
project=item.project,
item=item,
)
stock_items = self.get_stock_items()
warehouse_with_no_account = []
for d in self.get("items"):
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
if warehouse_account.get(d.warehouse):
stock_value_diff = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Purchase Receipt",
"voucher_no": self.name,
"voucher_detail_no": d.name,
"warehouse": d.warehouse,
"is_cancelled": 0,
},
"stock_value_difference",
if (
provisional_accounting_for_non_stock_items
and d.item_code not in stock_items
and flt(d.qty)
and d.get("provisional_expense_account")
and not d.is_fixed_asset
):
self.add_provisional_gl_entry(
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
)
elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
remarks = self.get("remarks") or _("Accounting Entry for {0}").format(
"Asset" if d.is_fixed_asset else "Stock"
)
if not (
(erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items)
or d.is_fixed_asset
):
continue
stock_asset_rbnb = (
self.get_company_default("asset_received_but_not_billed")
if d.is_fixed_asset
else self.get_company_default("stock_received_but_not_billed")
)
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
if d.is_fixed_asset:
account_type = (
"capital_work_in_progress_account"
if is_cwip_accounting_enabled(d.asset_category)
else "fixed_asset_account"
)
warehouse_account_name = warehouse_account[d.warehouse]["account"]
warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
stock_asset_account_name = get_asset_account(
account_type, asset_category=d.asset_category, company=self.company
)
stock_value_diff = (
flt(d.net_amount)
+ flt(d.item_tax_amount / self.conversion_rate)
+ flt(d.landed_cost_voucher_amount)
)
elif warehouse_account.get(d.warehouse):
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
stock_asset_account_name = warehouse_account[d.warehouse]["account"]
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get(
"account_currency"
)
remarks = self.get("remarks") or _("Accounting Entry for Stock")
# If PR is sub-contracted and fg item rate is zero
# in that case if account for source and target warehouse are same,
@@ -369,214 +590,25 @@ class PurchaseReceipt(BuyingController):
if (
flt(stock_value_diff) == flt(d.rm_supp_cost)
and warehouse_account.get(self.supplier_warehouse)
and warehouse_account_name == supplier_warehouse_account
and stock_asset_account_name == supplier_warehouse_account
):
continue
self.add_gl_entry(
gl_entries=gl_entries,
account=warehouse_account_name,
cost_center=d.cost_center,
debit=stock_value_diff,
credit=0.0,
remarks=remarks,
against_account=stock_rbnb,
account_currency=warehouse_account_currency,
item=d,
)
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
credit_currency = (
get_account_currency(warehouse_account[d.from_warehouse]["account"])
if d.from_warehouse
else get_account_currency(stock_rbnb)
)
credit_amount = (
flt(d.base_net_amount, d.precision("base_net_amount"))
if credit_currency == self.company_currency
else flt(d.net_amount, d.precision("net_amount"))
)
outgoing_amount = d.base_net_amount
if self.is_internal_transfer() and d.valuation_rate:
outgoing_amount = abs(
frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Purchase Receipt",
"voucher_no": self.name,
"voucher_detail_no": d.name,
"warehouse": d.from_warehouse,
"is_cancelled": 0,
},
"stock_value_difference",
)
)
credit_amount = outgoing_amount
if credit_amount:
account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=d.cost_center,
debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")),
credit=0.0,
remarks=remarks,
against_account=warehouse_account_name,
debit_in_account_currency=-1 * credit_amount,
account_currency=credit_currency,
item=d,
)
# check if the exchange rate has changed
if d.get("purchase_invoice"):
if (
exchange_rate_map[d.purchase_invoice]
and self.conversion_rate != exchange_rate_map[d.purchase_invoice]
and d.net_rate == net_rate_map[d.purchase_invoice_item]
):
discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * (
exchange_rate_map[d.purchase_invoice] - self.conversion_rate
)
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=d.cost_center,
debit=0.0,
credit=discrepancy_caused_by_exchange_rate_difference,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
item=d,
)
self.add_gl_entry(
gl_entries=gl_entries,
account=self.get_company_default("exchange_gain_loss_account"),
cost_center=d.cost_center,
debit=discrepancy_caused_by_exchange_rate_difference,
credit=0.0,
remarks=remarks,
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
item=d,
)
# Amount added through landed-cos-voucher
if d.landed_cost_voucher_amount and landed_cost_entries:
if (d.item_code, d.name) in landed_cost_entries:
for account, amount in landed_cost_entries[(d.item_code, d.name)].items():
account_currency = get_account_currency(account)
credit_amount = (
flt(amount["base_amount"])
if (amount["base_amount"] or account_currency != self.company_currency)
else flt(amount["amount"])
)
self.add_gl_entry(
gl_entries=gl_entries,
account=account,
cost_center=d.cost_center,
debit=0.0,
credit=credit_amount,
remarks=remarks,
against_account=warehouse_account_name,
credit_in_account_currency=flt(amount["amount"]),
account_currency=account_currency,
project=d.project,
item=d,
)
if d.rate_difference_with_purchase_invoice and stock_rbnb:
account_currency = get_account_currency(stock_rbnb)
self.add_gl_entry(
gl_entries=gl_entries,
account=stock_rbnb,
cost_center=d.cost_center,
debit=0.0,
credit=flt(d.rate_difference_with_purchase_invoice),
remarks=_("Adjustment based on Purchase Invoice rate"),
against_account=warehouse_account_name,
account_currency=account_currency,
project=d.project,
item=d,
)
# sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
self.add_gl_entry(
gl_entries=gl_entries,
account=supplier_warehouse_account,
cost_center=d.cost_center,
debit=0.0,
credit=flt(d.rm_supp_cost),
remarks=remarks,
against_account=warehouse_account_name,
account_currency=supplier_warehouse_account_currency,
item=d,
)
# divisional loss adjustment
valuation_amount_as_per_doc = (
flt(outgoing_amount, d.precision("base_net_amount"))
+ flt(d.landed_cost_voucher_amount)
+ flt(d.rm_supp_cost)
+ flt(d.item_tax_amount)
+ flt(d.rate_difference_with_purchase_invoice)
)
divisional_loss = flt(
valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount")
)
if divisional_loss:
if self.is_return or flt(d.item_tax_amount):
loss_account = expenses_included_in_valuation
else:
loss_account = (
self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
)
cost_center = d.cost_center or frappe.get_cached_value(
"Company", self.company, "cost_center"
)
self.add_gl_entry(
gl_entries=gl_entries,
account=loss_account,
cost_center=cost_center,
debit=divisional_loss,
credit=0.0,
remarks=remarks,
against_account=warehouse_account_name,
account_currency=credit_currency,
project=d.project,
item=d,
)
elif (
d.warehouse not in warehouse_with_no_account
or d.rejected_warehouse not in warehouse_with_no_account
):
warehouse_with_no_account.append(d.warehouse)
if (flt(d.valuation_rate) or self.is_return or d.is_fixed_asset) and flt(d.qty):
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
outgoing_amount = make_stock_received_but_not_billed_entry(d)
make_landed_cost_gl_entries(d)
make_rate_difference_entry(d)
make_sub_contracting_gl_entries(d)
make_divisional_loss_gl_entry(d, outgoing_amount)
elif (
d.item_code not in stock_items
and not d.is_fixed_asset
and flt(d.qty)
and provisional_accounting_for_non_stock_items
and d.get("provisional_expense_account")
d.warehouse not in warehouse_with_no_account
or d.rejected_warehouse not in warehouse_with_no_account
):
self.add_provisional_gl_entry(
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
)
warehouse_with_no_account.append(d.warehouse)
if d.is_fixed_asset:
self.update_assets(d, d.valuation_rate)
if warehouse_with_no_account:
frappe.msgprint(
@@ -589,8 +621,8 @@ class PurchaseReceipt(BuyingController):
self, item, gl_entries, posting_date, provisional_account, reverse=0
):
credit_currency = get_account_currency(provisional_account)
debit_currency = get_account_currency(item.expense_account)
expense_account = item.expense_account
debit_currency = get_account_currency(item.expense_account)
remarks = self.get("remarks") or _("Accounting Entry for Service")
multiplication_factor = 1
@@ -631,11 +663,8 @@ class PurchaseReceipt(BuyingController):
)
def make_tax_gl_entries(self, gl_entries):
if erpnext.is_perpetual_inventory_enabled(self.company):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
is_asset_pr = any(d.is_fixed_asset for d in self.get("items"))
# Cost center-wise amount breakup for other charges included for valuation
valuation_tax = {}
for tax in self.get("taxes"):
@@ -655,26 +684,26 @@ class PurchaseReceipt(BuyingController):
if negative_expense_to_be_booked and valuation_tax:
# Backward compatibility:
# If expenses_included_in_valuation account has been credited in against PI
# and charges added via Landed Cost Voucher,
# post valuation related charges on "Stock Received But Not Billed"
# introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account
negative_expense_booked_in_pi = frappe.db.sql(
"""select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
and voucher_no=pi.parent and account=%s)""",
(self.name, expenses_included_in_valuation),
)
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = negative_expense_to_be_booked
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
stock_rbnb = (
self.get("asset_received_but_not_billed")
if is_asset_pr
else self.get_company_default("stock_received_but_not_billed")
)
i = 1
for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
negative_expense_booked_in_pi = frappe.db.sql(
"""select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
and voucher_no=pi.parent and account=%s)""",
(self.name, tax.account_head),
)
if negative_expense_booked_in_pi:
account = stock_rbnb
@@ -702,103 +731,6 @@ class PurchaseReceipt(BuyingController):
i += 1
def get_asset_gl_entry(self, gl_entries):
for item in self.get("items"):
if item.is_fixed_asset:
if is_cwip_accounting_enabled(item.asset_category):
self.add_asset_gl_entries(item, gl_entries)
if flt(item.landed_cost_voucher_amount):
self.add_lcv_gl_entries(item, gl_entries)
# update assets gross amount by its valuation rate
# valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item
self.update_assets(item, item.valuation_rate)
return gl_entries
def add_asset_gl_entries(self, item, gl_entries):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
# This returns category's cwip account if not then fallback to company's default cwip account
cwip_account = get_asset_account(
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
)
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
remarks = self.get("remarks") or _("Accounting Entry for Asset")
cwip_account_currency = get_account_currency(cwip_account)
# debit cwip account
debit_in_account_currency = (
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
)
self.add_gl_entry(
gl_entries=gl_entries,
account=cwip_account,
cost_center=item.cost_center,
debit=base_asset_amount,
credit=0.0,
remarks=remarks,
against_account=arbnb_account,
debit_in_account_currency=debit_in_account_currency,
item=item,
)
asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account
credit_in_account_currency = (
base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount
)
self.add_gl_entry(
gl_entries=gl_entries,
account=arbnb_account,
cost_center=item.cost_center,
debit=0.0,
credit=base_asset_amount,
remarks=remarks,
against_account=cwip_account,
credit_in_account_currency=credit_in_account_currency,
item=item,
)
def add_lcv_gl_entries(self, item, gl_entries):
expenses_included_in_asset_valuation = self.get_company_default(
"expenses_included_in_asset_valuation"
)
if not is_cwip_accounting_enabled(item.asset_category):
asset_account = get_asset_category_account(
asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company
)
else:
# This returns company's default cwip account
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
remarks = self.get("remarks") or _("Accounting Entry for Stock")
self.add_gl_entry(
gl_entries=gl_entries,
account=expenses_included_in_asset_valuation,
cost_center=item.cost_center,
debit=0.0,
credit=flt(item.landed_cost_voucher_amount),
remarks=remarks,
against_account=asset_account,
project=item.project,
item=item,
)
self.add_gl_entry(
gl_entries=gl_entries,
account=asset_account,
cost_center=item.cost_center,
debit=flt(item.landed_cost_voucher_amount),
credit=0.0,
remarks=remarks,
against_account=expenses_included_in_asset_valuation,
project=item.project,
item=item,
)
def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all(
"Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code}
@@ -823,16 +755,28 @@ class PurchaseReceipt(BuyingController):
po_details.append(d.purchase_order_item)
if po_details:
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
updated_pr += update_billed_amount_based_on_po(po_details, update_modified, self)
for pr in set(updated_pr):
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
self.load_from_db()
def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse):
return frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Purchase Receipt",
"voucher_no": voucher_no,
"voucher_detail_no": voucher_detail_no,
"warehouse": warehouse,
"is_cancelled": 0,
},
"stock_value_difference",
)
def update_billed_amount_based_on_po(po_details, update_modified=True):
def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=None):
po_billed_amt_details = get_billed_amount_against_po(po_details)
# Get all Purchase Receipt Item rows against the Purchase Order Items
@@ -861,13 +805,19 @@ def update_billed_amount_based_on_po(po_details, update_modified=True):
po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po
if pr_item.billed_amt != billed_amt_agianst_pr:
frappe.db.set_value(
"Purchase Receipt Item",
pr_item.name,
"billed_amt",
billed_amt_agianst_pr,
update_modified=update_modified,
)
# update existing doc if possible
if pr_doc and pr_item.parent == pr_doc.name:
pr_item = next((item for item in pr_doc.items if item.name == pr_item.name), None)
pr_item.db_set("billed_amt", billed_amt_agianst_pr, update_modified=update_modified)
else:
frappe.db.set_value(
"Purchase Receipt Item",
pr_item.name,
"billed_amt",
billed_amt_agianst_pr,
update_modified=update_modified,
)
updated_pr.append(pr_item.parent)
@@ -943,9 +893,6 @@ def get_billed_amount_against_po(po_items):
def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
# Reload as billed amount was set in db directly
pr_doc.load_from_db()
# Update Billing % based on pending accepted qty
total_amount, total_billed_amount = 0, 0
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
@@ -971,7 +918,6 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
pr_doc.db_set("per_billed", percent_billed)
pr_doc.load_from_db()
if update_modified:
pr_doc.set_status(update=True)
@@ -1093,6 +1039,7 @@ def make_purchase_invoice(source_name, target_doc=None):
"is_fixed_asset": "is_fixed_asset",
"asset_location": "asset_location",
"asset_category": "asset_category",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"filter": lambda d: get_pending_qty(d)[0] <= 0
@@ -1250,3 +1197,8 @@ def get_item_account_wise_additional_cost(purchase_document):
def on_doctype_update():
frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])
@erpnext.allow_regional
def update_regional_gl_entries(gl_list, doc):
return

View File

@@ -928,17 +928,33 @@ class TestPurchaseReceipt(FrappeTestCase):
pr1.cancel()
def test_stock_transfer_from_purchase_receipt(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
prepare_data_for_internal_transfer()
customer = "_Test Internal Customer 2"
company = "_Test Company with perpetual inventory"
pr1 = make_purchase_receipt(
warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory"
warehouse="Stores - TCP1", company="_Test Company with perpetual inventory"
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
dn1 = create_delivery_note(
item_code=pr1.items[0].item_code,
company=company,
customer=customer,
cost_center="Main - TCP1",
expense_account="Cost of Goods Sold - TCP1",
qty=5,
rate=500,
warehouse="Stores - TCP1",
target_warehouse="Work In Progress - TCP1",
)
pr.supplier_warehouse = ""
pr = make_inter_company_purchase_receipt(dn1.name)
pr.items[0].from_warehouse = "Work In Progress - TCP1"
pr.items[0].warehouse = "Stores - TCP1"
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -955,6 +971,11 @@ class TestPurchaseReceipt(FrappeTestCase):
pr1.cancel()
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
prepare_data_for_internal_transfer()
create_warehouse(
"_Test Warehouse for Valuation",
company="_Test Company with perpetual inventory",
@@ -962,16 +983,28 @@ class TestPurchaseReceipt(FrappeTestCase):
)
pr1 = make_purchase_receipt(
warehouse="_Test Warehouse for Valuation - TCP1",
warehouse="Stores - TCP1",
company="_Test Company with perpetual inventory",
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
customer = "_Test Internal Customer 2"
company = "_Test Company with perpetual inventory"
dn1 = create_delivery_note(
item_code=pr1.items[0].item_code,
company=company,
customer=customer,
cost_center="Main - TCP1",
expense_account="Cost of Goods Sold - TCP1",
qty=5,
rate=50,
warehouse="Stores - TCP1",
target_warehouse="_Test Warehouse for Valuation - TCP1",
)
pr = make_inter_company_purchase_receipt(dn1.name)
pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
pr.supplier_warehouse = ""
pr.items[0].warehouse = "Stores - TCP1"
pr.append(
"taxes",
@@ -2147,6 +2180,77 @@ class TestPurchaseReceipt(FrappeTestCase):
# Test - 2: Valuation Rate should be equal to Previous SLE Valuation Rate
self.assertEqual(flt(sle.valuation_rate, 2), flt(previous_sle_valuation_rate, 2))
def test_purchase_return_with_zero_rate(self):
company = "_Test Company with perpetual inventory"
# Step - 1: Create Item
item, warehouse = (
make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name,
"Stores - TCP1",
)
# Step - 2: Create Stock Entry (Material Receipt)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
se = make_stock_entry(
purpose="Material Receipt",
item_code=item,
qty=100,
basic_rate=100,
to_warehouse=warehouse,
company=company,
)
# Step - 3: Create Purchase Receipt
pr = make_purchase_receipt(
item_code=item,
qty=5,
rate=0,
warehouse=warehouse,
company=company,
)
# Step - 4: Create Purchase Return
from erpnext.controllers.sales_and_purchase_return import make_return_doc
pr_return = make_return_doc("Purchase Receipt", pr.name)
pr_return.save()
pr_return.submit()
sl_entries = get_sl_entries(pr_return.doctype, pr_return.name)
gl_entries = get_gl_entries(pr_return.doctype, pr_return.name)
# Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate
average_rate = (
(se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)
) / (se.items[0].qty + pr.items[0].qty)
expected_stock_value_difference = pr_return.items[0].qty * average_rate
self.assertEqual(
flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2)
)
# Test - 2: GL Entries should be created for Stock Value Difference
self.assertEqual(len(gl_entries), 2)
# Test - 3: SLE Stock Value Difference should be equal to Debit or Credit of GL Entries.
for entry in gl_entries:
self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference))
def non_internal_transfer_purchase_receipt(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
pr_doc = make_purchase_receipt(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company)
pr_doc.items[0].db_set("target_warehouse", "warehouse")
pr_doc.reload()
self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name)
pr_doc.save()
pr_doc.reload()
self.assertFalse(pr_doc.items[0].from_warehouse)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -118,7 +118,9 @@
"dimension_col_break",
"cost_center",
"section_break_80",
"page_break"
"page_break",
"sales_order",
"sales_order_item"
],
"fields": [
{
@@ -921,7 +923,8 @@
"label": "Delivery Note Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"collapsible": 1,
@@ -1025,12 +1028,32 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "sales_order",
"fieldtype": "Link",
"label": "Sales Order",
"no_copy": 1,
"options": "Sales Order",
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Sales Order Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-10-03 21:11:50.547261",
"modified": "2023-10-30 17:32:24.560337",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -191,7 +191,7 @@ class SerialNo(StockController):
sle_dict = self.get_stock_ledger_entries(serial_no)
if sle_dict:
if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][0]
entries["purchase_sle"] = sle_dict["incoming"][-1]
if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
entries["last_sle"] = sle_dict["incoming"][0]

View File

@@ -451,31 +451,37 @@ class StockEntry(StockController):
item_code.append(item.item_code)
def validate_fg_completed_qty(self):
item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order:
for d in self.items:
if d.is_finished_item:
if self.process_loss_qty:
d.qty = self.fg_completed_qty - self.process_loss_qty
if self.purpose != "Manufacture":
return
item_wise_qty.setdefault(d.item_code, []).append(d.qty)
fg_qty = defaultdict(float)
for d in self.items:
if d.is_finished_item:
fg_qty[d.item_code] += flt(d.qty)
if not fg_qty:
return
precision = frappe.get_precision("Stock Entry Detail", "qty")
for item_code, qty_list in item_wise_qty.items():
total = flt(sum(qty_list), precision)
fg_item = list(fg_qty.keys())[0]
fg_item_qty = flt(fg_qty[fg_item], precision)
fg_completed_qty = flt(self.fg_completed_qty, precision)
if (self.fg_completed_qty - total) > 0 and not self.process_loss_qty:
self.process_loss_qty = flt(self.fg_completed_qty - total, precision)
self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty)
for d in self.items:
if not fg_qty.get(d.item_code):
continue
if self.process_loss_qty:
total += flt(self.process_loss_qty, precision)
if (fg_completed_qty - fg_item_qty) > 0:
self.process_loss_qty = fg_completed_qty - fg_item_qty
if self.fg_completed_qty != total:
if not self.process_loss_qty:
continue
if fg_completed_qty != (flt(fg_item_qty) + flt(self.process_loss_qty, precision)):
frappe.throw(
_("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
frappe.bold(item_code), frappe.bold(total), frappe.bold(self.fg_completed_qty)
)
_(
"Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table."
).format(frappe.bold(self.process_loss_qty), frappe.bold(d.item_code))
)
def validate_difference_account(self):
@@ -982,14 +988,34 @@ class StockEntry(StockController):
& (se.docstatus == 1)
& (se_detail.item_code == se_item.item_code)
& (
(se.purchase_order == self.purchase_order)
((se.purchase_order == self.purchase_order) & (se_detail.po_detail == se_item.po_detail))
if self.subcontract_data.order_doctype == "Purchase Order"
else (se.subcontracting_order == self.subcontracting_order)
else (
(se.subcontracting_order == self.subcontracting_order)
& (se_detail.sco_rm_detail == se_item.sco_rm_detail)
)
)
)
).run()[0][0]
).run()[0][0] or 0
if flt(total_supplied, precision) > flt(total_allowed, precision):
total_returned = 0
if self.subcontract_data.order_doctype == "Subcontracting Order":
total_returned = (
frappe.qb.from_(se)
.inner_join(se_detail)
.on(se.name == se_detail.parent)
.select(Sum(se_detail.transfer_qty))
.where(
(se.purpose == "Material Transfer")
& (se.docstatus == 1)
& (se.is_return == 1)
& (se_detail.item_code == se_item.item_code)
& (se_detail.sco_rm_detail == se_item.sco_rm_detail)
& (se.subcontracting_order == self.subcontracting_order)
)
).run()[0][0] or 0
if flt(total_supplied - total_returned, precision) > flt(total_allowed, precision):
frappe.throw(
_("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format(
se_item.idx,

View File

@@ -447,9 +447,7 @@ class TestStockEntry(FrappeTestCase):
repack.posting_date = nowdate()
repack.posting_time = nowtime()
expenses_included_in_valuation = frappe.get_value(
"Company", company, "expenses_included_in_valuation"
)
default_expense_account = frappe.get_value("Company", company, "default_expense_account")
items = get_multiple_items()
repack.items = []
@@ -460,12 +458,12 @@ class TestStockEntry(FrappeTestCase):
"additional_costs",
[
{
"expense_account": expenses_included_in_valuation,
"expense_account": default_expense_account,
"description": "Actual Operating Cost",
"amount": 1000,
},
{
"expense_account": expenses_included_in_valuation,
"expense_account": default_expense_account,
"description": "Additional Operating Cost",
"amount": 200,
},
@@ -504,9 +502,7 @@ class TestStockEntry(FrappeTestCase):
self.check_gl_entries(
"Stock Entry",
repack.name,
sorted(
[[stock_in_hand_account, 1200, 0.0], ["Expenses Included In Valuation - TCP1", 0.0, 1200.0]]
),
sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]),
)
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):

View File

@@ -95,13 +95,6 @@ frappe.ui.form.on("Stock Reconciliation", {
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
"get_query": function() {
return {
"filters": {
"disabled": 0,
}
};
}
},
{
label: __("Ignore Empty Stock"),

View File

@@ -647,7 +647,7 @@ class StockReconciliation(StockController):
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
"name",
)
and current_qty
and current_qty > 0
):
new_sle = self.get_sle_for_items(row)
new_sle.actual_qty = current_qty * -1

View File

@@ -956,6 +956,58 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
self.assertRaises(frappe.ValidationError, sr.save)
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_for_batch_item_dont_have_future_sle(self):
# Step - 1: Create a Batch Item
from erpnext.stock.doctype.item.test_item import make_item
item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TEST-BATCH-.###",
}
).name
# Step - 2: Create Opening Stock Reconciliation
sr1 = create_stock_reconciliation(
item_code=item,
warehouse="_Test Warehouse - _TC",
qty=10,
purpose="Opening Stock",
posting_date=add_days(nowdate(), -2),
)
# Step - 3: Create Stock Entry (Material Receipt)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
se1 = make_stock_entry(
item_code=item,
target="_Test Warehouse - _TC",
qty=100,
)
# Step - 4: Create Stock Entry (Material Issue)
make_stock_entry(
item_code=item,
source="_Test Warehouse - _TC",
qty=100,
batch_no=se1.items[0].batch_no,
purpose="Material Issue",
)
# Step - 5: Create Stock Reconciliation (Backdated) after the Stock Reconciliation 1 (Step - 2)
sr2 = create_stock_reconciliation(
item_code=item,
warehouse="_Test Warehouse - _TC",
qty=5,
batch_no=sr1.items[0].batch_no,
posting_date=add_days(nowdate(), -1),
)
self.assertEqual(sr2.docstatus, 1)
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)

View File

@@ -160,6 +160,7 @@ def update_stock(args, out):
and out.warehouse
and out.stock_qty > 0
):
out["ignore_serial_nos"] = args.get("ignore_serial_nos")
if out.has_batch_no and not args.get("batch_no"):
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
@@ -301,7 +302,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))
if item.variant_of:
if item.variant_of and not item.taxes:
item.update_template_tables()
item_defaults = get_item_defaults(item.name, args.company)
@@ -363,8 +364,12 @@ def get_basic_details(args, item, overwrite_warehouse=True):
),
"expense_account": expense_account
or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults),
"discount_account": get_default_discount_account(args, item_defaults),
"provisional_expense_account": get_provisional_account(args, item_defaults),
"discount_account": get_default_discount_account(
args, item_defaults, item_group_defaults, brand_defaults
),
"provisional_expense_account": get_provisional_account(
args, item_defaults, item_group_defaults, brand_defaults
),
"cost_center": get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
),
@@ -718,12 +723,22 @@ def get_default_expense_account(args, item, item_group, brand):
)
def get_provisional_account(args, item):
return item.get("default_provisional_account") or args.default_provisional_account
def get_provisional_account(args, item, item_group, brand):
return (
item.get("default_provisional_account")
or item_group.get("default_provisional_account")
or brand.get("default_provisional_account")
or args.default_provisional_account
)
def get_default_discount_account(args, item):
return item.get("default_discount_account") or args.discount_account
def get_default_discount_account(args, item, item_group, brand):
return (
item.get("default_discount_account")
or item_group.get("default_discount_account")
or brand.get("default_discount_account")
or args.discount_account
)
def get_default_deferred_account(args, item, fieldname=None):
@@ -770,6 +785,12 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
data = frappe.get_attr(path)(args.get("item_code"), company)
if data and (data.selling_cost_center or data.buying_cost_center):
if args.get("customer") and data.selling_cost_center:
return data.selling_cost_center
elif args.get("supplier") and data.buying_cost_center:
return data.buying_cost_center
return data.selling_cost_center or data.buying_cost_center
if not cost_center and args.get("cost_center"):
@@ -1140,6 +1161,8 @@ def get_serial_nos_by_fifo(args, sales_order=None):
query = query.where(sn.sales_order == sales_order)
if args.batch_no:
query = query.where(sn.batch_no == args.batch_no)
if args.ignore_serial_nos:
query = query.where(sn.name.notin(args.ignore_serial_nos))
serial_nos = query.run(as_list=True)
serial_nos = [s[0] for s in serial_nos]
@@ -1450,6 +1473,7 @@ def get_serial_no(args, serial_nos=None, sales_order=None):
"item_code": args.get("item_code"),
"warehouse": args.get("warehouse"),
"stock_qty": args.get("stock_qty"),
"ignore_serial_nos": args.get("ignore_serial_nos"),
}
)
args = process_args(args)

View File

@@ -699,7 +699,7 @@ class update_entries_after(object):
)
if self.valuation_method == "Moving Average":
rate = self.data[self.args.warehouse].previous_sle.valuation_rate
rate = flt(self.data[self.args.warehouse].previous_sle.valuation_rate)
else:
rate = get_rate_for_return(
sle.voucher_type,
@@ -1617,27 +1617,33 @@ def is_negative_with_precision(neg_sle, is_batch=False):
return qty_deficit < 0 and abs(qty_deficit) > 0.0001
def get_future_sle_with_negative_qty(args):
return frappe.db.sql(
"""
select
qty_after_transaction, posting_date, posting_time,
voucher_type, voucher_no
from `tabStock Ledger Entry`
where
item_code = %(item_code)s
and warehouse = %(warehouse)s
and voucher_no != %(voucher_no)s
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
and is_cancelled = 0
and qty_after_transaction < 0
order by timestamp(posting_date, posting_time) asc
limit 1
""",
args,
as_dict=1,
def get_future_sle_with_negative_qty(sle):
SLE = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(SLE)
.select(
SLE.qty_after_transaction, SLE.posting_date, SLE.posting_time, SLE.voucher_type, SLE.voucher_no
)
.where(
(SLE.item_code == sle.item_code)
& (SLE.warehouse == sle.warehouse)
& (SLE.voucher_no != sle.voucher_no)
& (
CombineDatetime(SLE.posting_date, SLE.posting_time)
>= CombineDatetime(sle.posting_date, sle.posting_time)
)
& (SLE.is_cancelled == 0)
& (SLE.qty_after_transaction < 0)
)
.orderby(CombineDatetime(SLE.posting_date, SLE.posting_time))
.limit(1)
)
if sle.voucher_type == "Stock Reconciliation" and sle.batch_no:
query = query.where(SLE.batch_no == sle.batch_no)
return query.run(as_dict=True)
def get_future_sle_with_negative_batch_qty(args):
return frappe.db.sql(

View File

@@ -107,7 +107,7 @@ frappe.ui.form.on('Subcontracting Order', {
get_materials_from_supplier: function (frm) {
let sco_rm_details = [];
if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) {
if (frm.doc.status != "Closed" && frm.doc.supplied_items) {
frm.doc.supplied_items.forEach(d => {
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
sco_rm_details.push(d.name);
@@ -193,7 +193,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
}
has_unsupplied_items() {
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
return this.frm.doc['supplied_items'].some(item => item.required_qty > (item.supplied_qty - item.returned_qty));
}
make_subcontracting_receipt() {

View File

@@ -299,7 +299,6 @@ class SubcontractingReceipt(SubcontractingController):
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_with_no_account = []
@@ -371,10 +370,7 @@ class SubcontractingReceipt(SubcontractingController):
divisional_loss = flt(item.amount - stock_value_diff, item.precision("amount"))
if divisional_loss:
if self.is_return:
loss_account = expenses_included_in_valuation
else:
loss_account = item.expense_account
loss_account = item.expense_account
self.add_gl_entry(
gl_entries=gl_entries,

View File

@@ -1,13 +1,6 @@
frappe.ui.form.on("Issue", {
onload: function(frm) {
frm.email_field = "raised_by";
frm.set_query("customer", function () {
return {
filters: {
"disabled": 0
}
};
});
frappe.db.get_value("Support Settings", {name: "Support Settings"},
["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {

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