Compare commits

...

160 Commits

Author SHA1 Message Date
Frappe PR Bot
ce90d427e8 chore(release): Bumped to Version 15.52.0
# [15.52.0](https://github.com/frappe/erpnext/compare/v15.51.2...v15.52.0) (2025-02-12)

### Bug Fixes

* '0' rate LDC's Invoice net totals should be ignored ([96c19cd](96c19cd990))
* add allow_on_submit for party_balance, paid_from_account_balance and paid_to_account_balance ([086c36f](086c36fca6))
* add precision in serial_batch_bundle.py ([a85f6f5](a85f6f54fe))
* add total row in non_grouped_invoices ([e432ae9](e432ae98a9))
* added validation for required invoice_fields in POS (backport [#45780](https://github.com/frappe/erpnext/issues/45780)) ([#45868](https://github.com/frappe/erpnext/issues/45868)) ([4f9a7f5](4f9a7f5065))
* Attibute error `selling_price_list` ([6dc99f9](6dc99f95c0))
* changed naming series to random for SABB ([48a4eff](48a4effdb6))
* check_item_quality_inspection is not whitelisted ([0a4a093](0a4a09352a))
* correct amount in transaction currency for reverse gl entries (backport [#45794](https://github.com/frappe/erpnext/issues/45794)) ([#45849](https://github.com/frappe/erpnext/issues/45849)) ([b06bd82](b06bd825c1))
* correct amt in account currency for lcv with manually distributed charges. (backport [#45532](https://github.com/frappe/erpnext/issues/45532)) ([#45864](https://github.com/frappe/erpnext/issues/45864)) ([179cb1e](179cb1e6e5))
* correct pay amount in portal pages ([3ada520](3ada520618))
* create job card with wip warehouse set to source warehouse if material transfer to wip warehouse is skipped in work order ([9d6f318](9d6f3180d4))
* do not allow "Finance Book" in Accounting Dimensions (backport [#45696](https://github.com/frappe/erpnext/issues/45696)) ([#45856](https://github.com/frappe/erpnext/issues/45856)) ([0954aca](0954aca758))
* do not validate party against  Receivable and Payable account for cancelled gl entries ([6d777cd](6d777cdc68))
* dont update rate of free item on save ([1d3da4d](1d3da4d49a))
* handle response when json is None ([eeb322b](eeb322bd0e))
* map project from rfq to supplier quotation (backport [#45745](https://github.com/frappe/erpnext/issues/45745)) ([#45828](https://github.com/frappe/erpnext/issues/45828)) ([b112d88](b112d88767))
* not able to select the item in the sales invoice ([a649001](a649001886))
* Party name in Supplier Portal for Purchase Order (backport [#45772](https://github.com/frappe/erpnext/issues/45772)) ([#45858](https://github.com/frappe/erpnext/issues/45858)) ([435c354](435c35414f))
* pos numpad editable action buttons (backport [#45823](https://github.com/frappe/erpnext/issues/45823)) ([#45826](https://github.com/frappe/erpnext/issues/45826)) ([035758f](035758f47d))
* possible model sync issue ([ea01fa1](ea01fa135e))
* **regional:** removed payment schedule validation in sales invoice for italy (backport [#45852](https://github.com/frappe/erpnext/issues/45852)) ([#45854](https://github.com/frappe/erpnext/issues/45854)) ([2e9e355](2e9e355329))
* remove serial no if qty is zero ([1359a77](1359a77e72))
* skip warning for free items ([2adab1d](2adab1d36f))
* stock reco current valuation rate ([3b7c38d](3b7c38da10))
* the project document timed out while opening ([28cbce4](28cbce4356))
* unable to remove image from employee ([e3cceb8](e3cceb894b))
* update ctx to args ([d4bc3d1](d4bc3d182f))

### Features

* add repost accounting ledger entry for payment entry ([757dd3f](757dd3f0b6))
2025-02-12 12:09:09 +00:00
ruthra kumar
0eb4556c83 Merge pull request #45860 from frappe/version-15-hotfix
chore: release v15
2025-02-12 17:37:43 +05:30
rohitwaghchaure
45fa2eb542 Merge pull request #45884 from frappe/mergify/bp/version-15-hotfix/pr-45786
fix: skip warning for free items (backport #45786)
2025-02-12 15:16:29 +05:30
barredterra
2adab1d36f fix: skip warning for free items
(cherry picked from commit 772776ad8a)
2025-02-12 09:20:15 +00:00
rohitwaghchaure
70bac1ab17 Merge pull request #45748 from frappe/mergify/bp/version-15-hotfix/pr-45739
fix: create job card with wip warehouse set to source warehouse if material transfer to wip warehouse is skipped in work order (backport #45739)
2025-02-12 13:00:19 +05:30
rohitwaghchaure
0caaff0758 Merge pull request #45880 from frappe/mergify/bp/version-15-hotfix/pr-45879
fix: changed naming series to random for SABB (backport #45879)
2025-02-12 10:53:47 +05:30
rohitwaghchaure
4094fbd6c5 chore: fix conflicts 2025-02-12 10:21:44 +05:30
Rohit Waghchaure
48a4effdb6 fix: changed naming series to random for SABB
(cherry picked from commit a007dc285d)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
2025-02-12 04:50:41 +00:00
rohitwaghchaure
68ea528841 Merge pull request #45877 from frappe/mergify/bp/version-15-hotfix/pr-45875
fix: add precision in serial_batch_bundle.py (backport #45875)
2025-02-12 09:25:31 +05:30
rohitwaghchaure
4aa7666e46 Merge pull request #45878 from frappe/mergify/bp/version-15-hotfix/pr-45865
fix: dont update rate of free item on save (backport #45865)
2025-02-12 09:25:08 +05:30
Mihir Kandoi
1d3da4d49a fix: dont update rate of free item on save
(cherry picked from commit 6591e76a63)
2025-02-12 03:13:41 +00:00
Mihir Kandoi
a85f6f54fe fix: add precision in serial_batch_bundle.py
(cherry picked from commit 4bf85d1a5a)
2025-02-12 03:06:27 +00:00
rohitwaghchaure
da08b4eeb9 Merge pull request #45870 from frappe/mergify/bp/version-15-hotfix/pr-45869
fix: stock reco current valuation rate (backport #45869)
2025-02-11 18:38:12 +05:30
rohitwaghchaure
b4f291d0ae Merge pull request #45871 from frappe/mergify/bp/version-15-hotfix/pr-45862
fix: remove serial no if qty is zero (backport #45862)
2025-02-11 18:21:21 +05:30
mergify[bot]
4f9a7f5065 fix: added validation for required invoice_fields in POS (backport #45780) (#45868)
* fix: added validation for required invoice_fields in POS (#45780)

fix: added missing validation for required invoice_fields
(cherry picked from commit b95b13ecd8)

# Conflicts:
#	erpnext/selling/page/point_of_sale/pos_payment.js

* fix: resolved merge conflict

---------

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-11 17:55:07 +05:30
Rohit Waghchaure
1359a77e72 fix: remove serial no if qty is zero
(cherry picked from commit 3a4ae8c463)
2025-02-11 11:28:16 +00:00
Rohit Waghchaure
3b7c38da10 fix: stock reco current valuation rate
(cherry picked from commit 8d8f3afb39)
2025-02-11 11:24:14 +00:00
mergify[bot]
179cb1e6e5 fix: correct amt in account currency for lcv with manually distributed charges. (backport #45532) (#45864)
fix: correct amt in account currency for lcv with manually distributed charges.

(cherry picked from commit db38e7bf5a)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 16:05:42 +05:30
mergify[bot]
435c35414f fix: Party name in Supplier Portal for Purchase Order (backport #45772) (#45858)
fix: Party name in Supplier Portal for Purchase Order

(cherry picked from commit fc8663421b)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:42:45 +05:30
mergify[bot]
0954aca758 fix: do not allow "Finance Book" in Accounting Dimensions (backport #45696) (#45856)
fix: do not allow "Finance Book" in Accounting Dimensions

(cherry picked from commit a44be73a98)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:42:36 +05:30
mergify[bot]
2e9e355329 fix(regional): removed payment schedule validation in sales invoice for italy (backport #45852) (#45854)
fix(regional): removed payment schedule validation in sales invoice for italy (#45852)

(cherry picked from commit 494310293c)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-02-11 15:41:30 +05:30
Smit Vora
6b8826f234 Merge pull request #45773 from ljain112/fix-portalpay
fix: correct pay amount in portal pages
2025-02-11 14:59:03 +05:30
mergify[bot]
b112d88767 fix: map project from rfq to supplier quotation (backport #45745) (#45828)
* fix: map project from rfq to supplier quotation

(cherry picked from commit d0479036bb)

* fix: add project field map from mr to rfq

(cherry picked from commit 8fa39bec61)

---------

Co-authored-by: HenningWendtland <156231187+HenningWendtland@users.noreply.github.com>
2025-02-11 14:52:13 +05:30
mergify[bot]
b06bd825c1 fix: correct amount in transaction currency for reverse gl entries (backport #45794) (#45849)
fix: correct amount in tansaction currency for reverse gl entries

(cherry picked from commit 6077c248b0)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 14:49:27 +05:30
ruthra kumar
b084433158 Merge pull request #45847 from frappe/mergify/bp/version-15-hotfix/pr-45792
fix: do not validate party against Receivable and Payable account for cancelled gl entries (backport #45792)
2025-02-11 12:53:29 +05:30
ljain112
6d777cdc68 fix: do not validate party against Receivable and Payable account for cancelled gl entries
(cherry picked from commit 0809e00455)
2025-02-11 06:44:50 +00:00
ruthra kumar
ebce6be23f Merge pull request #45844 from frappe/mergify/bp/version-15-hotfix/pr-45781
fix: Added Total Row for `Gross Profit` Report in Non-Grouped Invoices (backport #45781)
2025-02-11 12:01:11 +05:30
Sanket322
e432ae98a9 fix: add total row in non_grouped_invoices
(cherry picked from commit 2d32ddacc3)
2025-02-11 04:51:47 +00:00
ruthra kumar
09114e6a7b Merge pull request #45842 from frappe/mergify/bp/version-15-hotfix/pr-45832
fix: possible model sync issue (backport #45832)
2025-02-11 10:20:21 +05:30
ruthra kumar
ea01fa135e fix: possible model sync issue
(cherry picked from commit 0069581aa3)
2025-02-11 04:25:34 +00:00
Frappe PR Bot
b0c9fbe9fc chore(release): Bumped to Version 15.51.2
## [15.51.2](https://github.com/frappe/erpnext/compare/v15.51.1...v15.51.2) (2025-02-10)

### Bug Fixes

* Attibute error `selling_price_list` ([e8fd2ee](e8fd2eeaa2))
* check_item_quality_inspection is not whitelisted ([d0dbfa1](d0dbfa1cbd))
2025-02-10 16:28:24 +00:00
rohitwaghchaure
0758192c53 Merge pull request #45836 from frappe/mergify/bp/version-15/pr-45835
fix: check_item_quality_inspection is not whitelisted (backport #45835)
2025-02-10 21:56:59 +05:30
rohitwaghchaure
d2cb659220 Merge pull request #45834 from frappe/mergify/bp/version-15/pr-45822
fix: AttributeError in `get_item_details` when selecting/scanning batch in Delivery Note (backport #45774) (backport #45822)
2025-02-10 21:56:40 +05:30
Rohit Waghchaure
d0dbfa1cbd fix: check_item_quality_inspection is not whitelisted
(cherry picked from commit 0a4a09352a)
2025-02-10 15:46:46 +00:00
rohitwaghchaure
b9b2fc5773 Merge pull request #45835 from rohitwaghchaure/fixed-support-31452
fix: check_item_quality_inspection is not whitelisted
2025-02-10 21:15:27 +05:30
Rohit Waghchaure
0a4a09352a fix: check_item_quality_inspection is not whitelisted 2025-02-10 19:09:02 +05:30
DaizyModi
e8fd2eeaa2 fix: Attibute error selling_price_list
(cherry picked from commit 820b32eb8a)
(cherry picked from commit 6dc99f95c0)
2025-02-10 13:34:14 +00:00
rohitwaghchaure
51545234d3 Merge pull request #45822 from frappe/mergify/bp/version-15-hotfix/pr-45774
fix: AttributeError in `get_item_details` when selecting/scanning batch in Delivery Note (backport #45774)
2025-02-10 19:02:51 +05:30
mergify[bot]
035758f47d fix: pos numpad editable action buttons (backport #45823) (#45826)
fix: pos numpad editable action buttons (#45823)

(cherry picked from commit 0b9c28620f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-10 16:28:59 +05:30
DaizyModi
6dc99f95c0 fix: Attibute error selling_price_list
(cherry picked from commit 820b32eb8a)
2025-02-10 10:19:49 +00:00
ruthra kumar
f440795809 Merge pull request #45816 from frappe/mergify/bp/version-15-hotfix/pr-45747
fix: Handle Empty JSON in Report Parsing (backport #45747)
2025-02-10 12:10:43 +05:30
Sanket322
eeb322bd0e fix: handle response when json is None
(cherry picked from commit 133e0417b8)
2025-02-10 06:12:45 +00:00
ruthra kumar
9a5aa8eeb9 Merge pull request #45813 from frappe/mergify/bp/version-15-hotfix/pr-45793
fix: unable to remove image from employee (backport #45793)
2025-02-10 11:01:00 +05:30
ruthra kumar
1f967f7e9d Merge pull request #45814 from frappe/mergify/bp/version-15-hotfix/pr-45762
refactor: set received and paid amount based on each other, if unset (backport #45762)
2025-02-10 11:00:48 +05:30
rohitwaghchaure
3c386c2062 Merge pull request #45811 from frappe/mergify/bp/version-15-hotfix/pr-45810
fix: not able to select the item in the sales invoice (backport #45810)
2025-02-10 10:41:31 +05:30
ruthra kumar
1d6c50c9a1 chore: resolve conflict 2025-02-10 10:38:18 +05:30
ruthra kumar
e589c5b6ef refactor: set paid amount based on received amount if unset
(cherry picked from commit 99e721e622)
2025-02-10 05:08:05 +00:00
ruthra kumar
6638b391ff refactor: set received amount based on paid amount
(cherry picked from commit 5ff540bd82)
2025-02-10 05:08:05 +00:00
Asmita Hase
e3cceb894b fix: unable to remove image from employee
fix: employee image disappears when newly created user_id is linked to employee

(cherry picked from commit 0207d2d7b6)

# Conflicts:
#	erpnext/setup/doctype/employee/employee.json
2025-02-10 05:03:12 +00:00
Rohit Waghchaure
a649001886 fix: not able to select the item in the sales invoice
(cherry picked from commit 35388e7a04)
2025-02-10 04:39:40 +00:00
rohitwaghchaure
39e3746a62 Merge pull request #45775 from frappe/mergify/bp/version-15-hotfix/pr-45767
fix: the project document timed out while opening (backport #45767)
2025-02-10 10:09:38 +05:30
Frappe PR Bot
8c57e9f8c8 chore(release): Bumped to Version 15.51.1
## [15.51.1](https://github.com/frappe/erpnext/compare/v15.51.0...v15.51.1) (2025-02-07)

### Bug Fixes

* update ctx to args ([13bebe7](13bebe71b0))
2025-02-07 14:22:13 +00:00
ruthra kumar
5f62fc5a99 Merge pull request #45795 from frappe/mergify/bp/version-15/pr-45770
Fix: Update `ctx` to `args` for compatibility. (backport #45770)
2025-02-07 19:50:45 +05:30
Sanket322
13bebe71b0 fix: update ctx to args
(cherry picked from commit d4bc3d182f)
2025-02-07 14:14:41 +00:00
ruthra kumar
9702a192d7 Merge pull request #45770 from Sanket322/update_ctx_to_args
Fix: Update `ctx` to `args` for compatibility.
2025-02-07 19:43:53 +05:30
ruthra kumar
a051ddee6d Merge pull request #45788 from frappe/mergify/bp/version-15-hotfix/pr-45640
feat: add repost accounting ledger entry for payment entry (backport #45640)
2025-02-07 17:15:33 +05:30
ruthra kumar
be09700d8b Merge pull request #45789 from frappe/mergify/bp/version-15-hotfix/pr-45644
fix: add allow_on_submit for party_balance, paid_from_account_balance and paid_to_account_balance (backport #45644)
2025-02-07 17:15:22 +05:30
l0gesh29
757dd3f0b6 feat: add repost accounting ledger entry for payment entry
(cherry picked from commit 5676d60ed3)
2025-02-07 10:41:54 +00:00
Sugesh393
086c36fca6 fix: add allow_on_submit for party_balance, paid_from_account_balance and paid_to_account_balance
(cherry picked from commit 707c01487e)
2025-02-07 10:41:54 +00:00
ruthra kumar
a7d32b580f Merge pull request #45784 from frappe/mergify/bp/version-15-hotfix/pr-45639
fix: '0' rate LDC's Invoice net totals should be ignored (backport #45639)
2025-02-07 13:43:58 +05:30
ruthra kumar
43d75b96c6 chore: resolve conflict 2025-02-07 13:18:42 +05:30
ruthra kumar
3734289983 test: ldc @ 0 rate
(cherry picked from commit 0cdd346f8f)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
2025-02-07 07:44:11 +00:00
ruthra kumar
96c19cd990 fix: '0' rate LDC's Invoice net totals should be ignored
(cherry picked from commit 325c4e3536)
2025-02-07 07:44:11 +00:00
rohitwaghchaure
08a6f4e6d3 chore: fix conflicts 2025-02-06 22:09:44 +05:30
Rohit Waghchaure
28cbce4356 fix: the project document timed out while opening
(cherry picked from commit 33d03b1542)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.json
2025-02-06 16:38:46 +00:00
ljain112
3ada520618 fix: correct pay amount in portal pages 2025-02-06 18:56:29 +05:30
Sanket322
d4bc3d182f fix: update ctx to args 2025-02-06 17:55:37 +05:30
Mihir Kandoi
4196986273 chore: resolve conflicts 2025-02-05 17:59:34 +05:30
Mihir Kandoi
9d6f3180d4 fix: create job card with wip warehouse set to source warehouse if material transfer to wip warehouse is skipped in work order
(cherry picked from commit 723e902470)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py
2025-02-05 12:19:49 +00:00
Frappe PR Bot
b1161f446f chore(release): Bumped to Version 15.51.0
# [15.51.0](https://github.com/frappe/erpnext/compare/v15.50.1...v15.51.0) (2025-02-05)

### Bug Fixes

* actual qty showing blank for sub-assembly items ([d0748b1](d0748b1b67))
* added correct options for incoming_rate field of delivery note item ([cfc5007](cfc50073ed))
* allow multiple email ids ([2a25302](2a25302c35))
* attribute 'msgbox' not found in sales invoice.js ([f6b4984](f6b49845e2))
* bind this to function ([c0b32c4](c0b32c446b))
* check billing address ([44d9fb7](44d9fb7a69))
* closing stock balance report not generating ([47d1c3b](47d1c3b5a3))
* conflicts ([4aa072a](4aa072a8eb))
* consider process_loss_qty in work order ([1522d76](1522d76a3b))
* copy correct uom from devliery note when creating packing list ([f9794e5](f9794e5b44))
* correct error message in payment entry ([6ea5307](6ea5307b0e))
* default payment terms template selected while duplicating ([ca4bb96](ca4bb96fb4))
* Do not check for cancelled invoices ([84638f5](84638f58fd))
* Do not check for cancelled invoices ([b612ab5](b612ab5823))
* fetch rate from item price list when document is saved ([b140ce7](b140ce71d7))
* filter the item tax template using the input text ([8ea9a9e](8ea9a9e467))
* Gross Profit Report with Correct Totals and Gross Margin (backport [#45548](https://github.com/frappe/erpnext/issues/45548)) ([#45598](https://github.com/frappe/erpnext/issues/45598)) ([6301b32](6301b321d8))
* handling company in bank reconciliation tool ([#45582](https://github.com/frappe/erpnext/issues/45582)) ([aa27e19](aa27e19a58))
* ignore expired batch for pick list ([786db3d](786db3d0fa))
* loading print receipt only at order complete (backport [#45627](https://github.com/frappe/erpnext/issues/45627)) ([#45628](https://github.com/frappe/erpnext/issues/45628)) ([72868ee](72868eee04))
* logical error failing tests ([6a03f99](6a03f99546))
* not able to make manufacturing entry for alternate items ([eef2f3c](eef2f3c5d4))
* only system manager was able to create customer & prospect ([6149306](6149306b78))
* payment schedule table is empty while duplicating record ([c523625](c52362531c))
* point of sale padding (backport [#45697](https://github.com/frappe/erpnext/issues/45697)) ([#45699](https://github.com/frappe/erpnext/issues/45699)) ([b915e7f](b915e7f637))
* pos payment cash shortcut decimal (backport [#45702](https://github.com/frappe/erpnext/issues/45702)) ([#45705](https://github.com/frappe/erpnext/issues/45705)) ([6113cc1](6113cc1e43))
* pos print receipt on submit (backport [#45632](https://github.com/frappe/erpnext/issues/45632)) ([#45633](https://github.com/frappe/erpnext/issues/45633)) ([676dde5](676dde59c2))
* **pos:** add item in the existing item row when discount is applied ([0b54cb9](0b54cb9a7c))
* posting_date to posting_datetime in stock related queries ([cd5174e](cd5174e423))
* remove tds account in taxes table on change of Tax Withholding Category ([1ad16c3](1ad16c368e))
* removed unused field ([9a27d3c](9a27d3cedc))
* renamed Commments Tab to Notes tab in Lead doctype ([86edcfc](86edcfc1fd))
* reposting issue with s3 backup ([73c1bf9](73c1bf972e))
* resolved conflicts ([223fe62](223fe62638))
* respect user set account if not advance account for getting outstanding invoices in payment entry ([8108d7f](8108d7fdba))
* semgrep ([401fd7f](401fd7fca7))
* set asset value correctly after cancelling value adjustment ([7f7f403](7f7f403f5b))
* show only items with inspection enabled on create QI dialog ([34f8a37](34f8a370d1))
* slow SABB query ([7931c2d](7931c2d182))
* subcontracting valiation precision issue ([5319683](5319683dbf))
* track employee changes ([#45674](https://github.com/frappe/erpnext/issues/45674)) ([cf98ebf](cf98ebf7fe))
* validation message ([4742211](47422111b4))
* validation to prevent submission if the SABB is not linked to a stock transaction ([24a6f61](24a6f611d8))

### Features

* account heads changed along with journal entry type and descrip… ([#42845](https://github.com/frappe/erpnext/issues/42845)) ([bbecd36](bbecd36a5d))
* report to find incorrect SABB ([a948f2e](a948f2e095))
* set bank account of company to default company bank account from masters ([b840271](b840271d2a))

### Performance Improvements

* stock entry with batch (backport [#45486](https://github.com/frappe/erpnext/issues/45486)) ([#45602](https://github.com/frappe/erpnext/issues/45602)) ([8b75401](8b75401db9))
2025-02-05 12:08:00 +00:00
ruthra kumar
b8485e1d28 Merge pull request #45703 from frappe/version-15-hotfix
chore: release v15
2025-02-05 17:36:41 +05:30
ruthra kumar
e422ae16e5 Merge pull request #45743 from frappe/mergify/bp/version-15-hotfix/pr-45604
fix: filter the item tax template using the input text (backport #45604)
2025-02-05 17:13:07 +05:30
rohitwaghchaure
dcdd8e9824 Merge pull request #45742 from frappe/mergify/bp/version-15-hotfix/pr-45734
fix: added correct options for incoming_rate field of delivery note item (backport #45734)
2025-02-05 17:08:16 +05:30
rohitwaghchaure
9d98d0efc8 Merge pull request #45744 from frappe/mergify/bp/version-15-hotfix/pr-45741
fix: removed unused field (backport #45741)
2025-02-05 17:07:46 +05:30
rohitwaghchaure
af60368e54 Merge pull request #45740 from frappe/mergify/bp/version-15-hotfix/pr-45692
fix: fetch rate from item price list when document is saved (backport #45692)
2025-02-05 16:42:00 +05:30
rohitwaghchaure
ab37e5754b chore: fix conflicts 2025-02-05 16:40:20 +05:30
Rohit Waghchaure
9a27d3cedc fix: removed unused field
(cherry picked from commit 2d7a576da5)
2025-02-05 11:09:40 +00:00
ruthra kumar
0b591ba7eb chore: resolve conflicts 2025-02-05 16:37:09 +05:30
Bhavan23
8ea9a9e467 fix: filter the item tax template using the input text
(cherry picked from commit 4dd37ba033)

# Conflicts:
#	erpnext/controllers/queries.py
2025-02-05 11:01:21 +00:00
Mihir Kandoi
cfc50073ed fix: added correct options for incoming_rate field of delivery note item
(cherry picked from commit 417bf49a8d)

# Conflicts:
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
2025-02-05 10:48:01 +00:00
Mihir Kandoi
25075e5981 test: added test
(cherry picked from commit 07adfadd58)
2025-02-05 10:23:26 +00:00
Mihir Kandoi
6a03f99546 fix: logical error failing tests
(cherry picked from commit fee318a275)
2025-02-05 10:23:26 +00:00
Mihir Kandoi
b140ce71d7 fix: fetch rate from item price list when document is saved
(cherry picked from commit 1e4b9fbdf0)
2025-02-05 10:23:26 +00:00
Khushi Rawat
f72d4b6984 Merge pull request #45738 from frappe/mergify/bp/version-15-hotfix/pr-45735
fix: set asset value correctly after cancelling value adjustment (backport #45735)
2025-02-05 15:51:11 +05:30
ruthra kumar
d86f7839ab Merge pull request #45737 from frappe/mergify/bp/version-15-hotfix/pr-45717
fix(Purchase Invoice): default payment terms template selected while duplicating (backport #45717)
2025-02-05 15:30:48 +05:30
Khushi Rawat
7f7f403f5b fix: set asset value correctly after cancelling value adjustment
(cherry picked from commit fee3846144)
2025-02-05 09:56:11 +00:00
Ejaaz Khan
df0ca2d198 refactor: remove log
(cherry picked from commit bfc01441a0)
2025-02-05 09:47:59 +00:00
Ejaaz Khan
c52362531c fix: payment schedule table is empty while duplicating record
(cherry picked from commit fb3f08a441)
2025-02-05 09:47:59 +00:00
Ejaaz Khan
ca4bb96fb4 fix: default payment terms template selected while duplicating
(cherry picked from commit 18127603fe)
2025-02-05 09:47:59 +00:00
Khushi Rawat
50d10d5c8d Merge pull request #45733 from frappe/mergify/bp/version-15-hotfix/pr-42845
feat: account heads changed along with journal entry type and descrip… (backport #42845)
2025-02-05 15:03:27 +05:30
Khushi Rawat
4aa072a8eb fix: conflicts 2025-02-05 14:44:39 +05:30
Khushi Rawat
223fe62638 fix: resolved conflicts 2025-02-05 14:43:24 +05:30
rahulgupta8848
bbecd36a5d feat: account heads changed along with journal entry type and descrip… (#42845)
* feat: account heads changed along with journal entry type and description

* feat: added patch for difference_amount for asset value adjustment and refactor

---------

Co-authored-by: “rahulgupta8848” <“rahul.gupta@8848digital.com”>
(cherry picked from commit d4fdada83c)

# Conflicts:
#	erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json
#	erpnext/patches.txt
2025-02-05 09:09:49 +00:00
ruthra kumar
9f28993575 Merge pull request #45732 from frappe/mergify/bp/version-15-hotfix/pr-45590
fix: remove tds account in taxes table on change of Tax Withholding C… (backport #45590)
2025-02-05 14:28:07 +05:30
l0gesh29
1ad16c368e fix: remove tds account in taxes table on change of Tax Withholding Category
(cherry picked from commit 79b5a3e1dd)
2025-02-05 08:39:21 +00:00
ruthra kumar
c1002d3787 Merge pull request #45728 from frappe/mergify/bp/version-15-hotfix/pr-45686
fix: allow multiple email ids (backport #45686)
2025-02-05 14:08:48 +05:30
ruthra kumar
8ad32c4593 Merge pull request #45731 from frappe/mergify/bp/version-15-hotfix/pr-45447
fix(pos): add item in the existing item row when discount is applied (backport #45447)
2025-02-05 14:04:09 +05:30
venkat102
0b54cb9a7c fix(pos): add item in the existing item row when discount is applied
(cherry picked from commit bee2c04d0b)
2025-02-05 08:30:27 +00:00
Sudharsanan11
44d9fb7a69 fix: check billing address
(cherry picked from commit 9950e4aa0c)
2025-02-05 08:19:58 +00:00
Sudharsanan11
2a25302c35 fix: allow multiple email ids
(cherry picked from commit 423decb93c)
2025-02-05 08:19:58 +00:00
ruthra kumar
0cac9e9b9a Merge pull request #45727 from frappe/mergify/bp/version-15-hotfix/pr-45610
fix: respect user set account if not advance account for getting outsanding invoices in payment entry (backport #45610)
2025-02-05 13:40:05 +05:30
rohitwaghchaure
efa7602fae Merge pull request #45718 from frappe/mergify/bp/version-15-hotfix/pr-45710
fix: show only items with inspection enabled on create QI dialog (backport #45710)
2025-02-05 13:05:16 +05:30
ruthra kumar
b926b49afa chore: fix typo
(cherry picked from commit 85378f9d1a)
2025-02-05 06:40:22 +00:00
ljain112
8108d7fdba fix: respect user set account if not advance account for getting outstanding invoices in payment entry
(cherry picked from commit 9faf78d3e5)
2025-02-05 06:40:22 +00:00
Mihir Kandoi
3689a2deae chore: resolve conflicts 2025-02-05 12:00:01 +05:30
ruthra kumar
5a7b901327 Merge pull request #45719 from frappe/mergify/bp/version-15-hotfix/pr-45678
fix: copy correct uom from delivery note when creating packing list (backport #45678)
2025-02-05 10:38:13 +05:30
rohitwaghchaure
51b03d40aa Merge pull request #45721 from rohitwaghchaure/fixed-support-30732
fix: closing stock balance report not generating
2025-02-05 08:45:00 +05:30
Rohit Waghchaure
47d1c3b5a3 fix: closing stock balance report not generating 2025-02-05 05:29:23 +05:30
Mihir Kandoi
f9794e5b44 fix: copy correct uom from devliery note when creating packing list
(cherry picked from commit 3cdaa80526)
2025-02-04 19:06:15 +00:00
Mihir Kandoi
401fd7fca7 fix: semgrep
(cherry picked from commit ffd10d1fe9)
2025-02-04 19:04:57 +00:00
Mihir Kandoi
34f8a370d1 fix: show only items with inspection enabled on create QI dialog
(cherry picked from commit c92ec312b9)

# Conflicts:
#	erpnext/controllers/stock_controller.py
2025-02-04 19:04:57 +00:00
ruthra kumar
ac960d26e8 Merge pull request #45711 from frappe/mergify/bp/version-15-hotfix/pr-45582
fix: handling company in bank reconciliation tool (backport #45582)
2025-02-04 17:32:42 +05:30
Aayush Dalal
aa27e19a58 fix: handling company in bank reconciliation tool (#45582)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit d1c927530e)
2025-02-04 11:45:01 +00:00
rohitwaghchaure
1ed52c3b41 Merge pull request #45708 from frappe/mergify/bp/version-15-hotfix/pr-45701
fix: subcontracting validation precision issue (backport #45701)
2025-02-04 15:56:44 +05:30
rohitwaghchaure
f9af53cd87 Merge pull request #45706 from frappe/mergify/bp/version-15-hotfix/pr-45698
feat: report to find incorrect SABB (backport #45698)
2025-02-04 15:45:40 +05:30
Mihir Kandoi
5319683dbf fix: subcontracting valiation precision issue
(cherry picked from commit 8720d412bd)
2025-02-04 09:54:08 +00:00
Rohit Waghchaure
a948f2e095 feat: report to find incorrect SABB
(cherry picked from commit 7e24395e00)
2025-02-04 09:47:04 +00:00
mergify[bot]
6113cc1e43 fix: pos payment cash shortcut decimal (backport #45702) (#45705)
fix: pos payment cash shortcut decimal (#45702)

(cherry picked from commit a20116816e)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-04 15:14:08 +05:30
mergify[bot]
b915e7f637 fix: point of sale padding (backport #45697) (#45699)
fix: point of sale padding (#45697)

(cherry picked from commit 51a65899ec)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-04 14:02:38 +05:30
Shariq Ansari
0d03e03925 Merge pull request #45695 from frappe/mergify/bp/version-15-hotfix/pr-45694
fix: only system manager was able to create customer & prospect (backport #45694)
2025-02-04 12:48:50 +05:30
Shariq Ansari
6149306b78 fix: only system manager was able to create customer & prospect
(cherry picked from commit 716edeb465)
2025-02-04 06:41:09 +00:00
rohitwaghchaure
a0523c7956 Merge pull request #45690 from frappe/mergify/bp/version-15-hotfix/pr-45680
fix: slow SABB query (backport #45680)
2025-02-03 23:10:46 +05:30
Rohit Waghchaure
7931c2d182 fix: slow SABB query
(cherry picked from commit 81978a0bd8)
2025-02-03 17:02:03 +00:00
rohitwaghchaure
7658053dbd Merge pull request #45665 from frappe/mergify/bp/version-15-hotfix/pr-45648
fix: consider process_loss_qty in work order (backport #45648)
2025-02-03 20:57:01 +05:30
ruthra kumar
1b85cdcf48 Merge pull request #45677 from frappe/mergify/bp/version-15-hotfix/pr-45674
fix: track employee changes (backport #45674)
2025-02-03 15:27:03 +05:30
ruthra kumar
16a80cc04a Merge pull request #45675 from frappe/mergify/bp/version-15-hotfix/pr-45631
fix: Do not check for cancelled invoices (backport #45631)
2025-02-03 14:32:00 +05:30
Ankush Menat
cf98ebf7fe fix: track employee changes (#45674)
closes https://github.com/frappe/erpnext/issues/45571

(cherry picked from commit 827afbfa2e)
2025-02-03 08:52:45 +00:00
Deepesh Garg
84638f58fd fix: Do not check for cancelled invoices
(cherry picked from commit 2c94867b0e)
2025-02-03 08:42:28 +00:00
Deepesh Garg
b612ab5823 fix: Do not check for cancelled invoices
(cherry picked from commit 701fc02050)
2025-02-03 08:42:27 +00:00
ruthra kumar
0d94eba021 Merge pull request #45669 from frappe/mergify/bp/version-15-hotfix/pr-45619
feat: set bank account of company to default company bank account fro… (backport #45619)
2025-02-03 14:05:23 +05:30
Shariq Ansari
7d95113499 Merge pull request #45673 from frappe/mergify/bp/version-15-hotfix/pr-45637
fix: renamed Comments Tab to Notes tab in Lead doctype (backport #45637)
2025-02-03 13:27:26 +05:30
Shariq Ansari
afd2529c2a chore: resolved conflict 2025-02-03 13:26:33 +05:30
Shariq Ansari
86edcfc1fd fix: renamed Commments Tab to Notes tab in Lead doctype
(cherry picked from commit 018df3135a)

# Conflicts:
#	erpnext/crm/doctype/lead/lead.json
2025-02-03 07:53:32 +00:00
Mihir Kandoi
b840271d2a feat: set bank account of company to default company bank account from masters
(cherry picked from commit ce7702cc19)
2025-02-03 06:31:55 +00:00
rohitwaghchaure
55ac00a510 Merge pull request #45667 from frappe/mergify/bp/version-15-hotfix/pr-45621
fix: delivered button of purchase order (backport #45621)
2025-02-03 11:58:35 +05:30
Mihir Kandoi
c0b32c446b fix: bind this to function
(cherry picked from commit 41649cf52d)
2025-02-03 06:22:47 +00:00
Mihir Kandoi
1522d76a3b fix: consider process_loss_qty in work order
(cherry picked from commit 95fda47b6c)
2025-02-03 06:21:40 +00:00
rohitwaghchaure
83e676f128 Merge pull request #45647 from frappe/mergify/bp/version-15-hotfix/pr-45646
fix: not able to make manufacturing entry for alternate items (backport #45646)
2025-02-03 11:48:03 +05:30
Rohit Waghchaure
eef2f3c5d4 fix: not able to make manufacturing entry for alternate items
(cherry picked from commit 1607aa1a44)
2025-01-31 18:18:37 +00:00
rohitwaghchaure
821cc215e3 Merge pull request #45645 from frappe/mergify/bp/version-15-hotfix/pr-45642
fix: actual qty showing blank for sub-assembly items (backport #45642)
2025-01-31 23:22:09 +05:30
rohitwaghchaure
3e09abc495 Merge pull request #45617 from rtdany10/expired-batch-pick
fix: ignore expired batch for pick list
2025-01-31 22:49:34 +05:30
Rohit Waghchaure
d0748b1b67 fix: actual qty showing blank for sub-assembly items
(cherry picked from commit 5be2e71a35)
2025-01-31 17:09:00 +00:00
rohitwaghchaure
7fc70e3f20 Merge pull request #45638 from frappe/mergify/bp/version-15-hotfix/pr-45626
fix: validation to prevent submission, if the SABB is not linked to a stock transaction (backport #45626)
2025-01-31 15:21:43 +05:30
Rohit Waghchaure
24a6f611d8 fix: validation to prevent submission if the SABB is not linked to a stock transaction
(cherry picked from commit f976115a2b)
2025-01-31 09:14:53 +00:00
ruthra kumar
4b91b7691a Merge pull request #45636 from frappe/mergify/bp/version-15-hotfix/pr-45615
fix: correct error message in payment entry (backport #45615)
2025-01-31 14:18:59 +05:30
ljain112
6ea5307b0e fix: correct error message in payment entry
(cherry picked from commit 592704cfd0)
2025-01-31 07:27:37 +00:00
rohitwaghchaure
d65101a5f2 Merge pull request #45634 from frappe/mergify/bp/version-15-hotfix/pr-45629
fix: attribute 'msgbox' not found in sales invoice.js (backport #45629)
2025-01-31 12:11:28 +05:30
rohitwaghchaure
bd8e4eeddc chore: fix conflicts 2025-01-31 12:09:50 +05:30
Mihir Kandoi
f6b49845e2 fix: attribute 'msgbox' not found in sales invoice.js
(cherry picked from commit 5643385c22)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
2025-01-31 06:34:32 +00:00
mergify[bot]
676dde59c2 fix: pos print receipt on submit (backport #45632) (#45633)
fix: pos print receipt on submit (#45632)

(cherry picked from commit fe51535392)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-31 12:02:47 +05:30
mergify[bot]
72868eee04 fix: loading print receipt only at order complete (backport #45627) (#45628)
fix: loading print receipt only at order complete (#45627)

(cherry picked from commit 5a1851dfe3)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-31 11:19:23 +05:30
rohitwaghchaure
04c1fc71dd Merge pull request #45614 from frappe/mergify/bp/version-15-hotfix/pr-45612
fix: posting_date to posting_datetime in stock related queries (backport #45612)
2025-01-30 21:19:19 +05:30
Dany Robert
786db3d0fa fix: ignore expired batch for pick list 2025-01-30 19:34:06 +05:30
Rohit Waghchaure
cd5174e423 fix: posting_date to posting_datetime in stock related queries
(cherry picked from commit e61ab48145)
2025-01-30 11:36:10 +00:00
rohitwaghchaure
20d5a79839 Merge pull request #45613 from frappe/mergify/bp/version-15-hotfix/pr-45609
fix: reposting issue with s3 backup (backport #45609)
2025-01-30 17:05:16 +05:30
Rohit Waghchaure
73c1bf972e fix: reposting issue with s3 backup
(cherry picked from commit 6b454ca9a7)
2025-01-30 10:29:54 +00:00
mergify[bot]
6301b321d8 fix: Gross Profit Report with Correct Totals and Gross Margin (backport #45548) (#45598)
fix: Gross Profit Report with Correct Totals and Gross Margin (#45548)

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit aaf720ab61)

Co-authored-by: Sanket Shah <113279972+Sanket322@users.noreply.github.com>
2025-01-30 11:21:52 +05:30
mergify[bot]
8b75401db9 perf: stock entry with batch (backport #45486) (#45602)
perf: stock entry with batch

(cherry picked from commit 0b1b964b77)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2025-01-30 11:21:20 +05:30
rohitwaghchaure
b5856d4f1f Merge pull request #45601 from frappe/mergify/bp/version-15-hotfix/pr-45600
fix: validation message (backport #45600)
2025-01-29 20:48:56 +05:30
Rohit Waghchaure
47422111b4 fix: validation message
(cherry picked from commit 4c8dff942d)
2025-01-29 15:12:39 +00:00
87 changed files with 1259 additions and 321 deletions

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.50.1"
__version__ = "15.52.0"
def get_default_company(user=None):

View File

@@ -49,6 +49,7 @@ class AccountingDimension(Document):
"Accounting Dimension Detail",
"Company",
"Account",
"Finance Book",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)

View File

@@ -19,10 +19,15 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
if (!frm.doc.company) {
frm.set_value("company", frappe.defaults.get_default("company"));
}
// Set default filter dates
let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account");
},
@@ -98,7 +103,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
@@ -114,7 +119,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
@@ -130,7 +135,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {

View File

@@ -129,7 +129,7 @@ class GLEntry(Document):
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
if not (self.party_type and self.party):
if not self.is_cancelled and not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable":
frappe.throw(

View File

@@ -812,27 +812,41 @@ frappe.ui.form.on("Payment Entry", {
paid_amount: function (frm) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (!frm.doc.received_amount) {
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.paid_amount);
} else if (company_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.base_paid_amount);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
}
frm.trigger("reset_received_amount");
frm.events.hide_unhide_fields(frm);
},
received_amount: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frm.set_paid_amount_based_on_received_amount = true;
if (!frm.doc.paid_amount && frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("paid_amount", frm.doc.received_amount);
if (frm.doc.target_exchange_rate) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
}
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
}
frm.set_value(
"base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
);
if (!frm.doc.paid_amount) {
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("paid_amount", frm.doc.received_amount);
if (frm.doc.target_exchange_rate) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
}
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
} else if (company_currency == frm.doc.paid_from_account_currency) {
frm.set_value("paid_amount", frm.doc.base_received_amount);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
}
}
if (frm.doc.payment_type == "Pay")
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, true);
else frm.events.set_unallocated_amount(frm);

View File

@@ -224,6 +224,7 @@
"label": "Accounts"
},
{
"allow_on_submit": 1,
"depends_on": "party",
"fieldname": "party_balance",
"fieldtype": "Currency",
@@ -253,6 +254,7 @@
"reqd": 1
},
{
"allow_on_submit": 1,
"depends_on": "paid_from",
"fieldname": "paid_from_account_balance",
"fieldtype": "Currency",
@@ -286,6 +288,7 @@
"reqd": 1
},
{
"allow_on_submit": 1,
"depends_on": "paid_to",
"fieldname": "paid_to_account_balance",
"fieldtype": "Currency",
@@ -806,7 +809,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-01-13 16:03:47.169699",
"modified": "2025-01-31 17:27:28.555246",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -25,6 +25,10 @@ from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -114,6 +118,23 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def validate_for_repost(self):
validate_docs_for_voucher_types(["Payment Entry"])
validate_docs_for_deferred_accounting([self.name], [])
def on_update_after_submit(self):
# Flag will be set on Reconciliation
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
if self.flags.get("ignore_reposting_on_reconciliation"):
return
self.needs_repost = self.check_if_fields_updated(
fields_to_check=[], child_tables={"references": [], "taxes": [], "deductions": []}
)
if self.needs_repost:
self.validate_for_repost()
self.repost_accounting_entries()
def set_liability_account(self):
# Auto setting liability account should only be done during 'draft' status
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
@@ -543,7 +564,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(
comma_or(_(d) for d in valid_reference_doctypes)
comma_or([_(d) for d in valid_reference_doctypes])
)
)
@@ -1798,7 +1819,7 @@ class PaymentEntry(AccountsController):
paid_amount -= sum(flt(d.amount, precision) for d in self.deductions)
for ref in self.references:
reference_outstanding_amount = ref.outstanding_amount
reference_outstanding_amount = flt(ref.outstanding_amount)
abs_outstanding_amount = abs(reference_outstanding_amount)
if reference_outstanding_amount > 0:
@@ -2243,10 +2264,17 @@ def get_outstanding_reference_documents(args, validate=False):
outstanding_invoices = []
negative_outstanding_invoices = []
party_account = args.get("party_account")
# get party account if advance account is set.
if args.get("book_advance_payments_in_separate_party_account"):
party_account = get_party_account(args.get("party_type"), args.get("party"), args.get("company"))
else:
party_account = args.get("party_account")
accounts = get_party_account(
args.get("party_type"), args.get("party"), args.get("company"), include_advance=True
)
advance_account = accounts[1] if len(accounts) >= 1 else None
if party_account == advance_account:
party_account = accounts[0]
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
@@ -2826,6 +2854,7 @@ def get_payment_entry(
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(

View File

@@ -224,9 +224,6 @@ class TestPOSClosingEntry(unittest.TestCase):
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
)
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
self.assertEqual(batch_qty, 10)
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 0.0)
@@ -256,9 +253,6 @@ class TestPOSClosingEntry(unittest.TestCase):
pcv_doc.reload()
pcv_doc.cancel()
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
self.assertEqual(batch_qty, 10)
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 0.0)

View File

@@ -321,9 +321,12 @@ def get_recipients_and_cc(customer, doc):
recipients = []
for clist in doc.customers:
if clist.customer == customer:
recipients.append(clist.billing_email)
if clist.billing_email:
for email in clist.billing_email.split(","):
recipients.append(email.strip())
if doc.primary_mandatory and clist.primary_email:
recipients.append(clist.primary_email)
for email in clist.primary_email.split(","):
recipients.append(email.strip())
cc = []
if doc.cc_to != "":
try:

View File

@@ -332,6 +332,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
let payment_terms_template = this.frm.doc.payment_terms_template;
erpnext.utils.get_party_details(
this.frm,
"erpnext.accounts.party.get_party_details",
@@ -352,6 +354,12 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
// while duplicating, don't change payment terms
if (me.frm.doc.__run_link_triggers === false) {
me.frm.set_value("payment_terms_template", payment_terms_template);
me.frm.refresh_field("payment_terms_template");
}
}
);
}
@@ -368,6 +376,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
}
tax_withholding_category(frm) {
var me = this;
let filtered_taxes = (me.frm.doc.taxes || []).filter((row) => !row.is_tax_withholding_account);
me.frm.clear_table("taxes");
filtered_taxes.forEach((row) => {
me.frm.add_child("taxes", row);
});
me.frm.refresh_field("taxes");
}
credit_to() {
var me = this;
if (this.frm.doc.credit_to) {

View File

@@ -65,9 +65,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
refresh(doc, dt, dn) {
const me = this;
super.refresh();
if (cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
if (this.frm?.msgbox && this.frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
cur_frm.msgbox.hide();
this.frm.msgbox.hide();
}
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);

View File

@@ -299,7 +299,8 @@
"oldfieldname": "project_name",
"oldfieldtype": "Link",
"options": "Project",
"print_hide": 1
"print_hide": 1,
"search_index": 1
},
{
"default": "0",
@@ -2186,7 +2187,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-01-14 11:38:30.446370",
"modified": "2025-02-06 15:59:54.636202",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -634,9 +634,7 @@ class Subscription(Document):
"""
invoice = frappe.get_all(
self.invoice_document_type,
{
"subscription": self.name,
},
{"subscription": self.name, "docstatus": ("<", 2)},
limit=1,
order_by="to_date desc",
pluck="name",
@@ -675,6 +673,7 @@ class Subscription(Document):
self.invoice_document_type,
{
"subscription": self.name,
"docstatus": 1,
"status": ["!=", "Paid"],
},
)

View File

@@ -270,7 +270,10 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no):
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers, voucher_wise_amount = get_invoice_vouchers(
parties, tax_details, inv.company, party_type=party_type
parties,
tax_details,
inv.company,
party_type=party_type,
)
payment_entry_vouchers = get_payment_entry_vouchers(
@@ -360,11 +363,23 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
voucher_wise_amount = []
vouchers = []
ldcs = frappe.db.get_all(
"Lower Deduction Certificate",
filters={
"valid_from": [">=", tax_details.from_date],
"valid_upto": ["<=", tax_details.to_date],
"company": company,
"supplier": ["in", parties],
},
fields=["supplier", "valid_from", "valid_upto", "rate"],
)
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = [
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
"name",
"grand_total",
"posting_date",
]
filters = {
@@ -383,18 +398,23 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
invoices_details = frappe.get_all(doctype, filters=filters, fields=field)
for d in invoices_details:
vouchers.append(d.name)
voucher_wise_amount.append(
frappe._dict(
{
"voucher_name": d.name,
"voucher_type": doctype,
"taxable_amount": d.base_net_total,
"grand_total": d.grand_total,
}
)
d = frappe._dict(
{
"voucher_name": d.name,
"voucher_type": doctype,
"taxable_amount": d.base_net_total,
"grand_total": d.grand_total,
"posting_date": d.posting_date,
}
)
if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]:
if ldc[0].supplier in parties and ldc[0].rate == 0:
d.update({"taxable_amount": 0})
vouchers.append(d.voucher_name)
voucher_wise_amount.append(d)
journal_entries_details = frappe.db.sql(
"""
SELECT j.name, ja.credit - ja.debit AS amount, ja.reference_type

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today
from frappe.utils import add_days, add_months, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
@@ -666,6 +666,49 @@ class TestTaxWithholdingCategory(FrappeTestCase):
pi2.cancel()
pi3.cancel()
def test_ldc_at_0_rate(self):
frappe.db.set_value(
"Supplier",
"Test LDC Supplier",
{
"tax_withholding_category": "Test Service Category",
"pan": "ABCTY1234D",
},
)
fiscal_year = get_fiscal_year(today(), company="_Test Company")
valid_from = fiscal_year[1]
valid_upto = add_months(valid_from, 1)
create_lower_deduction_certificate(
supplier="Test LDC Supplier",
certificate_no="1AE0423AAJ",
tax_withholding_category="Test Service Category",
tax_rate=0,
limit=50000,
valid_from=valid_from,
valid_upto=valid_upto,
)
pi1 = create_purchase_invoice(
supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True
)
pi1.submit()
self.assertEqual(pi1.taxes, [])
pi2 = create_purchase_invoice(
supplier="Test LDC Supplier",
rate=35000,
posting_date=add_days(valid_upto, 1),
set_posting_time=True,
)
pi2.submit()
self.assertEqual(len(pi2.taxes), 1)
# pi1 net total shouldn't be included as it lies within LDC at rate of '0'
self.assertEqual(pi2.taxes[0].tax_amount, 3500)
pi1.cancel()
pi2.cancel()
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
@@ -823,7 +866,8 @@ def create_purchase_invoice(**args):
pi = frappe.get_doc(
{
"doctype": "Purchase Invoice",
"posting_date": today(),
"set_posting_time": args.set_posting_time or False,
"posting_date": args.posting_date or today(),
"apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": "_Test Company",
@@ -1161,7 +1205,9 @@ def create_tax_withholding_category(
).insert()
def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit):
def create_lower_deduction_certificate(
supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None
):
fiscal_year = get_fiscal_year(today(), company="_Test Company")
if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
frappe.get_doc(
@@ -1172,8 +1218,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r
"certificate_no": certificate_no,
"tax_withholding_category": tax_withholding_category,
"fiscal_year": fiscal_year[0],
"valid_from": fiscal_year[1],
"valid_upto": fiscal_year[2],
"valid_from": valid_from or fiscal_year[1],
"valid_upto": valid_upto or fiscal_year[2],
"rate": tax_rate,
"certificate_limit": limit,
}

View File

@@ -680,11 +680,15 @@ def make_reverse_gl_entries(
debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
debit_in_transaction_currency = new_gle.get("debit_in_transaction_currency", 0)
credit_in_transaction_currency = new_gle.get("credit_in_transaction_currency", 0)
new_gle["debit"] = credit
new_gle["credit"] = debit
new_gle["debit_in_account_currency"] = credit_in_account_currency
new_gle["credit_in_account_currency"] = debit_in_account_currency
new_gle["debit_in_transaction_currency"] = credit_in_transaction_currency
new_gle["credit_in_transaction_currency"] = debit_in_transaction_currency
new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
new_gle["is_cancelled"] = 1

View File

@@ -1,5 +1,5 @@
{
"add_total_row": 1,
"add_total_row": 0,
"columns": [],
"creation": "2013-02-25 17:03:34",
"disable_prepared_report": 0,
@@ -9,7 +9,7 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
"modified": "2022-02-11 10:18:36.956558",
"modified": "2025-01-27 18:40:24.493829",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",

View File

@@ -178,7 +178,14 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
# removing Item Code and Item Name columns
del columns[4:6]
total_base_amount = 0
total_buying_amount = 0
for src in gross_profit_data.si_list:
if src.indent == 1:
total_base_amount += src.base_amount or 0.0
total_buying_amount += src.buying_amount or 0.0
row = frappe._dict()
row.indent = src.indent
row.parent_invoice = src.parent_invoice
@@ -189,17 +196,57 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
data.append(
frappe._dict(
{
"sales_invoice": "Total",
"qty": None,
"avg._selling_rate": None,
"valuation_rate": None,
"selling_amount": total_base_amount,
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_%": flt(
(total_gross_profit / total_base_amount) * 100.0,
cint(frappe.db.get_default("currency_precision")) or 3,
)
if total_base_amount
else 0,
}
)
)
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
for src in gross_profit_data.grouped_data:
row = []
for col in group_wise_columns.get(scrub(filters.group_by)):
row.append(src.get(col))
total_base_amount = 0
total_buying_amount = 0
row.append(filters.currency)
group_columns = group_wise_columns.get(scrub(filters.group_by))
for src in gross_profit_data.grouped_data:
total_base_amount += src.base_amount or 0.00
total_buying_amount += src.buying_amount or 0.00
row = [src.get(col) for col in group_columns] + [filters.currency]
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
gross_profit_percent = (total_gross_profit / total_base_amount * 100.0) if total_base_amount else 0
total_row = {
group_columns[0]: "Total",
"base_amount": total_base_amount,
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_percent": flt(gross_profit_percent, currency_precision),
}
total_row = [total_row.get(col, None) for col in [*group_columns, "currency"]]
data.append(total_row)
def get_columns(group_wise_columns, filters):
columns = []

View File

@@ -605,3 +605,33 @@ class TestGrossProfit(FrappeTestCase):
item_from_sinv2 = [x for x in data if x.parent_invoice == sinv2.name]
self.assertEqual(len(item_from_sinv2), 1)
self.assertEqual(1800, item_from_sinv2[0].valuation_rate)
def test_gross_profit_groupby_invoices(self):
create_sales_invoice(
qty=1,
rate=100,
company=self.company,
customer=self.customer,
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",
income_account=self.income_account,
expense_account=self.expense_account,
)
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)

View File

@@ -773,6 +773,8 @@ def update_reference_in_payment_entry(
frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict
)
# Ledgers will be reposted by Reconciliation tool
payment_entry.flags.ignore_reposting_on_reconciliation = True
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row, update_advance_paid

View File

@@ -1069,7 +1069,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation:
value_after_depreciation = row.value_after_depreciation + difference_amount
value_after_depreciation = row.value_after_depreciation - difference_amount
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
"Written Down Value",

View File

@@ -5,7 +5,6 @@ frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Asset Value Adjustment", {
setup: function (frm) {
frm.add_fetch("company", "cost_center", "cost_center");
frm.set_query("cost_center", function () {
return {
filters: {
@@ -22,6 +21,14 @@ frappe.ui.form.on("Asset Value Adjustment", {
},
};
});
frm.set_query("difference_account", function () {
return {
filters: {
company: frm.doc.company,
is_group: 0,
},
};
});
},
onload: function (frm) {
@@ -37,7 +44,7 @@ frappe.ui.form.on("Asset Value Adjustment", {
},
asset: function (frm) {
frm.trigger("set_current_asset_value");
frm.trigger("set_acc_dimension");
},
finance_book: function (frm) {
@@ -60,4 +67,15 @@ frappe.ui.form.on("Asset Value Adjustment", {
});
}
},
set_acc_dimension: function (frm) {
if (frm.doc.asset) {
frm.call({
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_value_of_accounting_dimensions",
args: {
asset_name: frm.doc.asset,
},
});
}
},
});

View File

@@ -17,6 +17,7 @@
"new_asset_value",
"column_break_11",
"difference_amount",
"difference_account",
"journal_entry",
"accounting_dimensions_section",
"cost_center",
@@ -54,6 +55,7 @@
"fieldtype": "Link",
"label": "Journal Entry",
"options": "Journal Entry",
"no_copy": 1,
"read_only": 1
},
{
@@ -79,6 +81,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "New Asset Value",
"no_copy": 1,
"reqd": 1
},
{
@@ -120,12 +123,20 @@
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "difference_account",
"fieldtype": "Link",
"label": "Difference Account",
"no_copy": 1,
"options": "Account",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-01-22 14:10:23.085181",
"modified": "2024-08-13 16:21:18.639208",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Value Adjustment",
@@ -182,4 +193,4 @@
"sort_order": "DESC",
"title_field": "asset",
"track_changes": 1
}
}

View File

@@ -34,6 +34,7 @@ class AssetValueAdjustment(Document):
cost_center: DF.Link | None
current_asset_value: DF.Currency
date: DF.Date
difference_account: DF.Link
difference_amount: DF.Currency
finance_book: DF.Link | None
journal_entry: DF.Link | None
@@ -47,6 +48,7 @@ class AssetValueAdjustment(Document):
def on_submit(self):
self.make_depreciation_entry()
self.set_value_after_depreciation()
self.update_asset(self.new_asset_value)
add_asset_activity(
self.asset,
@@ -76,7 +78,10 @@ class AssetValueAdjustment(Document):
)
def set_difference_amount(self):
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
self.difference_amount = flt(self.new_asset_value - self.current_asset_value)
def set_value_after_depreciation(self):
frappe.db.set_value("Asset", self.asset, "value_after_depreciation", self.new_asset_value)
def set_current_asset_value(self):
if not self.current_asset_value and self.asset:
@@ -85,7 +90,7 @@ class AssetValueAdjustment(Document):
def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset)
(
_,
fixed_asset_account,
accumulated_depreciation_account,
depreciation_expense_account,
) = get_depreciation_accounts(asset.asset_category, asset.company)
@@ -95,28 +100,41 @@ class AssetValueAdjustment(Document):
)
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Depreciation Entry"
je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series
je.posting_date = self.date
je.company = self.company
je.remark = f"Depreciation Entry against {self.asset} worth {self.difference_amount}"
je.remark = f"Revaluation Entry against {self.asset} worth {self.difference_amount}"
je.finance_book = self.finance_book
credit_entry = {
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
entry_template = {
"cost_center": self.cost_center or depreciation_cost_center,
"reference_type": "Asset",
"reference_name": self.asset,
"reference_name": asset.name,
}
debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": self.asset,
}
if self.difference_amount < 0:
credit_entry = {
"account": fixed_asset_account,
"credit_in_account_currency": -self.difference_amount,
**entry_template,
}
debit_entry = {
"account": self.difference_account,
"debit_in_account_currency": -self.difference_amount,
**entry_template,
}
elif self.difference_amount > 0:
credit_entry = {
"account": self.difference_account,
"credit_in_account_currency": self.difference_amount,
**entry_template,
}
debit_entry = {
"account": fixed_asset_account,
"debit_in_account_currency": self.difference_amount,
**entry_template,
}
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
@@ -179,3 +197,9 @@ class AssetValueAdjustment(Document):
)
asset.flags.ignore_validate_update_after_submit = True
asset.save()
@frappe.whitelist()
def get_value_of_accounting_dimensions(asset_name):
dimension_fields = [*frappe.get_list("Accounting Dimension", pluck="fieldname"), "cost_center"]
return frappe.db.get_value("Asset", asset_name, fieldname=dimension_fields, as_dict=True)

View File

@@ -93,8 +93,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
("_Test Depreciations - _TC", 4625.29, 0.0),
("_Test Difference Account - _TC", 4625.29, 0.0),
("_Test Fixed Asset - _TC", 0.0, 4625.29),
)
gle = frappe.db.sql(
@@ -177,8 +177,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
# Test gl entry creted from asset value adjustemnet
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 5625.29),
("_Test Depreciations - _TC", 5625.29, 0.0),
("_Test Difference Account - _TC", 5625.29, 0.0),
("_Test Fixed Asset - _TC", 0.0, 5625.29),
)
gle = frappe.db.sql(
@@ -259,6 +259,39 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertEqual(schedules, expected_schedules)
def test_difference_amount(self):
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset_doc = frappe.get_doc("Asset", asset_name)
asset_doc.calculate_depreciation = 1
asset_doc.available_for_use_date = "2023-01-15"
asset_doc.purchase_date = "2023-01-15"
asset_doc.append(
"finance_books",
{
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 1,
"depreciation_start_date": "2023-01-31",
},
)
asset_doc.submit()
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name,
current_asset_value=54000,
new_asset_value=50000.0,
date="2023-08-21",
)
adj_doc.submit()
difference_amount = adj_doc.new_asset_value - adj_doc.current_asset_value
self.assertEqual(difference_amount, -4000)
asset_doc.load_from_db()
self.assertEqual(asset_doc.value_after_depreciation, 50000.0)
def make_asset_value_adjustment(**args):
args = frappe._dict(args)
@@ -272,7 +305,22 @@ def make_asset_value_adjustment(**args):
"new_asset_value": args.new_asset_value,
"current_asset_value": args.current_asset_value,
"cost_center": args.cost_center or "Main - _TC",
"difference_account": make_difference_account(),
}
).insert()
return doc
def make_difference_account(**args):
account = "_Test Difference Account - _TC"
if not frappe.db.exists("Account", account):
acc = frappe.new_doc("Account")
acc.account_name = "_Test Difference Account"
acc.parent_account = "Direct Income - _TC"
acc.company = "_Test Company"
acc.is_group = 0
acc.insert()
return acc.name
else:
return account

View File

@@ -367,7 +367,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
}
if (is_drop_ship && doc.status != "Delivered") {
this.frm.add_custom_button(__("Delivered"), this.delivered_by_supplier, __("Status"));
this.frm.add_custom_button(
__("Delivered"),
this.delivered_by_supplier.bind(this),
__("Status")
);
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
}

View File

@@ -394,7 +394,11 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=
},
"Request for Quotation Item": {
"doctype": "Supplier Quotation Item",
"field_map": {"name": "request_for_quotation_item", "parent": "request_for_quotation"},
"field_map": {
"name": "request_for_quotation_item",
"parent": "request_for_quotation",
"project_name": "project",
},
},
},
target_doc,

View File

@@ -821,6 +821,9 @@ class AccountsController(TransactionBase):
and item.get("use_serial_batch_fields")
)
):
if fieldname == "batch_no" and not item.batch_no and not item.is_free_item:
item.set("rate", ret.get("rate"))
item.set("price_list_rate", ret.get("price_list_rate"))
item.set(fieldname, value)
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(
@@ -1900,22 +1903,22 @@ class AccountsController(TransactionBase):
continue
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
based_on_amt = flt(item.get(based_on))
if not ref_amt:
frappe.msgprint(
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
item.item_code, ref_dt
),
title=_("Warning"),
indicator="orange",
)
if based_on_amt: # Skip warning for free items
frappe.msgprint(
_(
"System will not check over billing since amount for Item {0} in {1} is zero"
).format(item.item_code, ref_dt),
title=_("Warning"),
indicator="orange",
)
continue
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
total_billed_amt = flt(
flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)
)
total_billed_amt = flt(flt(already_billed) + based_on_amt, self.precision(based_on, item))
allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"

View File

@@ -807,7 +807,27 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_group = item_group_doc.parent_item_group
if not taxes:
return frappe.get_all("Item Tax Template", filters={"disabled": 0, "company": company}, as_list=True)
or_filters = []
if txt:
search_fields = ["name"]
tax_template_doc = frappe.get_meta("Item Tax Template")
if title_field := tax_template_doc.title_field:
search_fields.append(title_field)
if tax_template_doc.search_fields:
search_fields.extend(tax_template_doc.get_search_fields())
for f in search_fields:
or_filters.append([doctype, f.strip(), "like", f"%{txt}%"])
return frappe.get_list(
"Item Tax Template",
filters={"disabled": 0, "company": company},
or_filters=or_filters,
as_list=True,
)
else:
valid_from = filters.get("valid_from")
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
@@ -820,7 +840,8 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)
return [(d,) for d in set(taxes)]
txt = txt.lower()
return [(d,) for d in set(taxes) if not txt or txt in d.lower()]
def get_fields(doctype, fields=None):

View File

@@ -333,7 +333,7 @@ class SellingController(StockController):
"batch_no": p.batch_no if self.docstatus == 2 else None,
"uom": p.uom,
"serial_and_batch_bundle": p.serial_and_batch_bundle
or get_serial_and_batch_bundle(p, self),
or get_serial_and_batch_bundle(p, self, d),
"name": d.name,
"target_warehouse": p.target_warehouse,
"company": self.company,
@@ -799,7 +799,7 @@ def set_default_income_account_for_item(obj):
set_item_default(d.item_code, obj.company, "income_account", d.income_account)
def get_serial_and_batch_bundle(child, parent):
def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
if child.get("use_serial_batch_fields"):
@@ -819,7 +819,7 @@ def get_serial_and_batch_bundle(child, parent):
"warehouse": child.warehouse,
"voucher_type": parent.doctype,
"voucher_no": parent.name if parent.docstatus < 2 else None,
"voucher_detail_no": child.name,
"voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name,
"posting_date": parent.posting_date,
"posting_time": parent.posting_time,
"qty": child.qty,

View File

@@ -216,6 +216,10 @@ class StockController(AccountsController):
if self.doctype == "Asset Capitalization":
table_name = "stock_items"
parent_details = frappe._dict()
if table_name == "packed_items":
parent_details = self.get_parent_details_for_packed_items()
for row in self.get(table_name):
if row.serial_and_batch_bundle and (row.serial_no or row.batch_no):
self.validate_serial_nos_and_batches_with_bundle(row)
@@ -246,13 +250,20 @@ class StockController(AccountsController):
}
if row.get("qty") or row.get("consumed_qty") or row.get("stock_qty"):
self.update_bundle_details(bundle_details, table_name, row)
self.update_bundle_details(bundle_details, table_name, row, parent_details=parent_details)
self.create_serial_batch_bundle(bundle_details, row)
if row.get("rejected_qty"):
self.update_bundle_details(bundle_details, table_name, row, is_rejected=True)
self.create_serial_batch_bundle(bundle_details, row)
def get_parent_details_for_packed_items(self):
parent_details = frappe._dict()
for row in self.get("items"):
parent_details[row.name] = row
return parent_details
def make_bundle_for_sales_purchase_return(self, table_name=None):
if not self.get("is_return"):
return
@@ -387,7 +398,7 @@ class StockController(AccountsController):
return False
def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False):
def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False, parent_details=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
# Since qty field is different for different doctypes
@@ -429,6 +440,11 @@ class StockController(AccountsController):
warehouse = row.get("target_warehouse") or row.get("warehouse")
type_of_transaction = "Outward"
if table_name == "packed_items":
if not warehouse:
warehouse = parent_details[row.parent_detail_docname].warehouse
bundle_details["voucher_detail_no"] = parent_details[row.parent_detail_docname].name
bundle_details.update(
{
"qty": qty,
@@ -921,9 +937,11 @@ class StockController(AccountsController):
row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.serial_batch_bundle import update_batch_qty
from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
update_batch_qty(self.doctype, self.name, via_landed_cost_voucher=via_landed_cost_voucher)
def make_gl_entries_on_cancel(self):
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
@@ -1546,6 +1564,28 @@ def repost_required_for_queue(doc: StockController) -> bool:
return False
@frappe.whitelist()
def check_item_quality_inspection(doctype, items):
if isinstance(items, str):
items = json.loads(items)
inspection_fieldname_map = {
"Purchase Receipt": "inspection_required_before_purchase",
"Purchase Invoice": "inspection_required_before_purchase",
"Subcontracting Receipt": "inspection_required_before_purchase",
"Sales Invoice": "inspection_required_before_delivery",
"Delivery Note": "inspection_required_before_delivery",
}
items_to_remove = []
for item in items:
if not frappe.db.get_value("Item", item.get("item_code"), inspection_fieldname_map.get(doctype)):
items_to_remove.append(item)
items = [item for item in items if item not in items_to_remove]
return items
@frappe.whitelist()
def make_quality_inspections(doctype, docname, items):
if isinstance(items, str):

View File

@@ -113,11 +113,10 @@ class SubcontractingController(StockController):
)
item.sc_conversion_factor = service_item_qty / item.qty
if (
self.doctype not in "Subcontracting Receipt"
and item.qty
> flt(get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item))
/ item.sc_conversion_factor
if self.doctype not in "Subcontracting Receipt" and item.qty > flt(
get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item)
/ item.sc_conversion_factor,
frappe.get_precision("Purchase Order Item", "qty"),
):
frappe.throw(
_(

View File

@@ -377,7 +377,7 @@
"depends_on": "eval:!doc.__islocal",
"fieldname": "notes_tab",
"fieldtype": "Tab Break",
"label": "Comments"
"label": "Notes"
},
{
"collapsible": 1,
@@ -516,7 +516,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2023-12-01 18:46:49.468526",
"modified": "2025-01-31 13:40:08.094759",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -584,4 +584,4 @@
"states": [],
"subject_field": "title",
"title_field": "title"
}
}

View File

@@ -31,7 +31,6 @@ def create_custom_fields_for_frappe_crm():
@frappe.whitelist()
def create_prospect_against_crm_deal():
frappe.only_for("System Manager")
doc = frappe.form_dict
prospect = frappe.get_doc(
{
@@ -152,7 +151,6 @@ def contact_exists(email, mobile_no):
@frappe.whitelist()
def create_customer(customer_data=None):
frappe.only_for("System Manager")
if not customer_data:
customer_data = frappe.form_dict

View File

@@ -562,6 +562,8 @@ accounting_dimension_doctypes = [
"Payment Reconciliation",
"Payment Reconciliation Allocation",
"Payment Request",
"Asset Movement Item",
"Asset Depreciation Schedule",
]
get_matching_queries = (

View File

@@ -982,7 +982,9 @@ class JobCard(Document):
if self.time_logs:
self.status = "Work In Progress"
if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
if self.docstatus == 1 and (
self.for_quantity <= (self.total_completed_qty + self.process_loss_qty) or not self.items
):
self.status = "Completed"
if update_status:

View File

@@ -516,6 +516,7 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(jc.status, status)
jc = frappe.new_doc("Job Card")
jc.process_loss_qty = 0
jc.for_quantity = 2
jc.transferred_qty = 1
jc.total_completed_qty = 0

View File

@@ -15,7 +15,6 @@
"bom_section",
"update_bom_costs_automatically",
"column_break_lhyt",
"manufacture_sub_assembly_in_operation",
"section_break_6",
"default_wip_warehouse",
"default_fg_warehouse",
@@ -223,13 +222,6 @@
"fieldname": "column_break_lhyt",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled then system will manufacture Sub-assembly against the Job Card (operation).",
"fieldname": "manufacture_sub_assembly_in_operation",
"fieldtype": "Check",
"label": "Manufacture Sub-assembly in Operation"
},
{
"default": "0",
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
@@ -249,7 +241,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-13 12:07:03.089977",
"modified": "2025-02-05 16:11:11.639916",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",

View File

@@ -29,7 +29,6 @@ class ManufacturingSettings(Document):
get_rm_cost_from_consumption_entry: DF.Check
job_card_excess_transfer: DF.Check
make_serial_no_batch_from_work_order: DF.Check
manufacture_sub_assembly_in_operation: DF.Check
material_consumption: DF.Check
mins_between_operations: DF.Int
overproduction_percentage_for_sales_order: DF.Percent

View File

@@ -562,6 +562,28 @@ frappe.ui.form.on("Production Plan Sales Order", {
frappe.ui.form.on("Production Plan Sub Assembly Item", {
fg_warehouse(frm, cdt, cdn) {
erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse");
let row = locals[cdt][cdn];
if (row.fg_warehouse && row.production_item) {
let child_row = {
item_code: row.production_item,
warehouse: row.fg_warehouse,
};
frappe.call({
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_bin_details",
args: {
row: child_row,
company: frm.doc.company,
for_warehouse: row.fg_warehouse,
},
callback: function (r) {
if (r.message && r.message.length) {
frappe.model.set_value(cdt, cdn, "actual_qty", r.message[0].actual_qty);
}
},
});
}
},
});

View File

@@ -937,8 +937,14 @@ class ProductionPlan(Document):
bom_data = []
warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
get_sub_assembly_items(
row.bom_no,
bom_data,
row.planned_qty,
self.company,
warehouse=self.sub_assembly_warehouse,
skip_available_sub_assembly_item=self.skip_available_sub_assembly_item,
)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
@@ -1729,14 +1735,23 @@ def get_item_data(item_code):
}
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=None, indent=0):
def get_sub_assembly_items(
bom_no,
bom_data,
to_produce_qty,
company,
warehouse=None,
indent=0,
skip_available_sub_assembly_item=False,
):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
if warehouse:
bin_details = frappe._dict()
if skip_available_sub_assembly_item:
bin_details = get_bin_details(d, company, for_warehouse=warehouse)
for _bin_dict in bin_details:
@@ -1746,11 +1761,14 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=
continue
else:
stock_qty = stock_qty - _bin_dict.projected_qty
elif warehouse:
bin_details = get_bin_details(d, company, for_warehouse=warehouse)
if stock_qty > 0:
bom_data.append(
frappe._dict(
{
"actual_qty": bin_details[0].get("actual_qty", 0) if bin_details else 0,
"parent_item_code": parent_item_code,
"description": d.description,
"production_item": d.item_code,
@@ -1768,7 +1786,13 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=
if d.value:
get_sub_assembly_items(
d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1
d.value,
bom_data,
stock_qty,
company,
warehouse,
indent=indent + 1,
skip_available_sub_assembly_item=skip_available_sub_assembly_item,
)

View File

@@ -397,7 +397,10 @@ frappe.ui.form.on("Work Order", {
message = title;
// pending qty
if (!frm.doc.skip_transfer) {
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
var pending_complete =
frm.doc.material_transferred_for_manufacturing -
frm.doc.produced_qty -
frm.doc.process_loss_qty;
if (pending_complete) {
var width = (pending_complete / frm.doc.qty) * 100 - added_min;
title = __("{0} items in progress", [pending_complete]);
@@ -409,6 +412,16 @@ frappe.ui.form.on("Work Order", {
message = message + ". " + title;
}
}
if (frm.doc.process_loss_qty) {
var process_loss_width = (frm.doc.process_loss_qty / frm.doc.qty) * 100;
title = __("{0} items lost during process.", [frm.doc.process_loss_qty]);
bars.push({
title: title,
width: process_loss_width + "%",
progress_class: "progress-bar-danger",
});
message = message + ". " + title;
}
frm.dashboard.add_progress(__("Status"), bars, message);
},

View File

@@ -1519,7 +1519,9 @@ def close_work_order(work_order, status):
work_order = frappe.get_doc("Work Order", work_order)
if work_order.get("operations"):
job_cards = frappe.get_list(
"Job Card", filters={"work_order": work_order.name, "status": "Work In Progress"}, pluck="name"
"Job Card",
filters={"work_order": work_order.name, "status": "Work In Progress", "docstatus": 1},
pluck="name",
)
if job_cards:
@@ -1618,7 +1620,9 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
"project": work_order.project,
"company": work_order.company,
"sequence_id": row.get("sequence_id"),
"wip_warehouse": work_order.wip_warehouse,
"wip_warehouse": work_order.wip_warehouse or row.get("wip_warehouse")
if not work_order.skip_transfer or work_order.from_wip_warehouse
else work_order.source_warehouse or row.get("source_warehouse"),
"hour_rate": row.get("hour_rate"),
"serial_no": row.get("serial_no"),
}

View File

@@ -391,3 +391,5 @@ erpnext.patches.v15_0.rename_manufacturing_settings_field
erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
erpnext.patches.v15_0.sync_auto_reconcile_config
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
erpnext.patches.v14_0.disable_add_row_in_gross_profit
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment

View File

@@ -0,0 +1,5 @@
import frappe
def execute():
frappe.db.set_value("Report", "Gross Profit", "add_total_row", 0)

View File

@@ -27,7 +27,7 @@ def update_reference_reports(reference_report):
def update_report_json(report):
report_json = json.loads(report.json)
report_json = json.loads(report.json) if report.get("json") else {}
report_filter = report_json.get("filters")
if not report_filter:

View File

@@ -0,0 +1,10 @@
import frappe
def execute():
AssetValueAdjustment = frappe.qb.DocType("Asset Value Adjustment")
frappe.qb.update(AssetValueAdjustment).set(
AssetValueAdjustment.difference_amount,
AssetValueAdjustment.new_asset_value - AssetValueAdjustment.current_asset_value,
).where(AssetValueAdjustment.docstatus != 2).run()

View File

@@ -11,16 +11,17 @@ def execute():
frappe.db.set_single_value("Accounts Settings", "reconciliation_queue_size", 5)
# Create Scheduler Event record if it doesn't exist
method = "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs"
if not frappe.db.get_all(
"Scheduler Event", {"scheduled_against": "Process Payment Reconciliation", "method": method}
):
frappe.get_doc(
{
"doctype": "Scheduler Event",
"scheduled_against": "Process Payment Reconciliation",
"method": method,
}
).save()
if frappe.reload_doc("core", "doctype", "scheduler_event"):
method = "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs"
if not frappe.db.get_all(
"Scheduler Event", {"scheduled_against": "Process Payment Reconciliation", "method": method}
):
frappe.get_doc(
{
"doctype": "Scheduler Event",
"scheduled_against": "Process Payment Reconciliation",
"method": method,
}
).save()
sync_auto_reconcile_config(15)
sync_auto_reconcile_config(15)

View File

@@ -86,8 +86,6 @@ class Project(Document):
),
)
self.update_costing()
def before_print(self, settings=None):
self.onload()

View File

@@ -2367,29 +2367,39 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
primary_action_label: __("Create")
});
this.frm.doc.items.forEach(item => {
if (this.has_inspection_required(item)) {
let dialog_items = dialog.fields_dict.items;
dialog_items.df.data.push({
"item_code": item.item_code,
"item_name": item.item_name,
"qty": item.qty,
"description": item.description,
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"sample_size": item.sample_quantity,
"child_row_reference": item.name,
frappe.call({
method: "erpnext.controllers.stock_controller.check_item_quality_inspection",
args: {
doctype: this.frm.doc.doctype,
items: this.frm.doc.items
},
freeze: true,
callback: function (r) {
r.message.forEach(item => {
if (me.has_inspection_required(item)) {
let dialog_items = dialog.fields_dict.items;
dialog_items.df.data.push({
"item_code": item.item_code,
"item_name": item.item_name,
"qty": item.qty,
"description": item.description,
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"sample_size": item.sample_quantity,
"child_row_reference": item.name,
});
dialog_items.grid.refresh();
}
});
dialog_items.grid.refresh();
data = dialog.fields_dict.items.df.data;
if (!data.length) {
frappe.msgprint(__("All items in this document already have a linked Quality Inspection."));
} else {
dialog.show();
}
}
});
data = dialog.fields_dict.items.df.data;
if (!data.length) {
frappe.msgprint(__("All items in this document already have a linked Quality Inspection."));
} else {
dialog.show();
}
}
has_inspection_required(item) {

View File

@@ -661,7 +661,7 @@ erpnext.utils.update_child_items = function (opts) {
method: "erpnext.stock.get_item_details.get_item_details",
args: {
doc: frm.doc,
ctx: {
args: {
item_code: this.value,
set_warehouse: frm.doc.set_warehouse,
customer: frm.doc.customer || frm.doc.party_name,

View File

@@ -2,6 +2,7 @@
display: grid;
grid-template-columns: repeat(10, minmax(0, 1fr));
gap: var(--margin-md);
padding: 1%;
section {
min-height: 45rem;

View File

@@ -331,22 +331,19 @@ def sales_invoice_on_submit(doc, method):
]:
return
if not len(doc.payment_schedule):
frappe.throw(_("Please set the Payment Schedule"), title=_("E-Invoicing Information Missing"))
else:
for schedule in doc.payment_schedule:
if not schedule.mode_of_payment:
frappe.throw(
_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
title=_("E-Invoicing Information Missing"),
)
elif not frappe.db.get_value("Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"):
frappe.throw(
_("Row {0}: Please set the correct code on Mode of Payment {1}").format(
schedule.idx, schedule.mode_of_payment
),
title=_("E-Invoicing Information Missing"),
)
for schedule in doc.payment_schedule:
if not schedule.mode_of_payment:
frappe.throw(
_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
title=_("E-Invoicing Information Missing"),
)
elif not frappe.db.get_value("Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"):
frappe.throw(
_("Row {0}: Please set the correct code on Mode of Payment {1}").format(
schedule.idx, schedule.mode_of_payment
),
title=_("E-Invoicing Information Missing"),
)
prepare_and_attach_invoice(doc)

View File

@@ -1151,7 +1151,8 @@
"label": "Project",
"oldfieldname": "project",
"oldfieldtype": "Link",
"options": "Project"
"options": "Project",
"search_index": 1
},
{
"fieldname": "party_account_currency",
@@ -1654,7 +1655,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-11-26 12:42:06.872527",
"modified": "2025-02-06 16:02:20.320877",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1732,4 +1733,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -485,6 +485,7 @@ erpnext.PointOfSale.Controller = class {
]);
},
},
pos_profile: this.pos_profile,
});
}
@@ -681,7 +682,7 @@ erpnext.PointOfSale.Controller = class {
i.item_code === item_code &&
(!has_batch_no || (has_batch_no && i.batch_no === batch_no)) &&
i.uom === uom &&
i.rate === flt(rate)
i.price_list_rate === flt(rate)
);
}

View File

@@ -748,6 +748,7 @@ erpnext.PointOfSale.ItemCart = class {
frappe.utils.play_sound("error");
return;
}
this.highlight_numpad_btn($btn, current_action);
if (first_click_event || field_to_edit_changed) {
this.prev_action = current_action;
@@ -793,7 +794,6 @@ erpnext.PointOfSale.ItemCart = class {
this.numpad_value = current_action;
}
this.highlight_numpad_btn($btn, current_action);
this.events.numpad_event(this.numpad_value, this.prev_action);
}

View File

@@ -357,7 +357,9 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.add_summary_btns(condition_btns_map);
this.print_receipt_on_order_complete();
if (after_submission) {
this.print_receipt_on_order_complete();
}
}
attach_document_info(doc) {

View File

@@ -41,6 +41,7 @@ erpnext.PointOfSale.Payment = class {
}
make_invoice_fields_control() {
this.reqd_invoice_fields = [];
frappe.db.get_doc("POS Settings", undefined).then((doc) => {
const fields = doc.invoice_fields;
if (!fields.length) return;
@@ -67,6 +68,9 @@ erpnext.PointOfSale.Payment = class {
},
};
}
if (df.reqd && (df.fieldtype !== "Button" || !df.read_only)) {
this.reqd_invoice_fields.push({ fieldname: df.fieldname, label: df.label });
}
this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
df: {
@@ -204,7 +208,11 @@ erpnext.PointOfSale.Payment = class {
const paid_amount = doc.paid_amount;
const items = doc.items;
if (paid_amount == 0 || !items.length) {
if (!this.validate_reqd_invoice_fields()) {
return;
}
if (!items.length || (paid_amount == 0 && doc.additional_discount_percentage != 100)) {
const message = items.length
? __("You cannot submit the order without payment.")
: __("You cannot submit empty order.");
@@ -462,7 +470,7 @@ erpnext.PointOfSale.Payment = class {
this.$payment_modes.find(".cash-shortcuts").remove();
let shortcuts_html = shortcuts
.map((s) => {
return `<div class="shortcut" data-value="${s}">${format_currency(s, currency, 0)}</div>`;
return `<div class="shortcut" data-value="${s}">${format_currency(s, currency)}</div>`;
})
.join("");
@@ -620,4 +628,20 @@ erpnext.PointOfSale.Payment = class {
.replace(/^[^_a-zA-Z\p{L}]+/u, "")
.toLowerCase();
}
validate_reqd_invoice_fields() {
const doc = this.events.get_frm().doc;
let validation_flag = true;
for (let field of this.reqd_invoice_fields) {
if (!doc[field.fieldname]) {
validation_flag = false;
frappe.show_alert({
message: __("{0} is a mandatory field.", [field.label]),
indicator: "orange",
});
frappe.utils.play_sound("error");
}
}
return validation_flag;
}
};

View File

@@ -182,8 +182,6 @@
"read_only": 1
},
{
"fetch_from": "user_id.user_image",
"fetch_if_empty": 1,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
@@ -824,7 +822,7 @@
"image_field": "image",
"is_tree": 1,
"links": [],
"modified": "2024-01-03 17:36:20.984421",
"modified": "2025-02-07 13:54:40.122345",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
@@ -871,5 +869,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "employee_name"
"title_field": "employee_name",
"track_changes": 1
}

View File

@@ -64,14 +64,12 @@ class Employee(NestedSet):
def validate_user_details(self):
if self.user_id:
data = frappe.db.get_value("User", self.user_id, ["enabled", "user_image"], as_dict=1)
data = frappe.db.get_value("User", self.user_id, ["enabled"], as_dict=1)
if not data:
self.user_id = None
return
if data.get("user_image") and self.image == "":
self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id()

View File

@@ -169,7 +169,7 @@ class DeprecatedBatchNoValuation:
if not self.non_batchwise_balance_qty:
continue
if self.non_batchwise_balance_qty.get(batch_no) == 0:
if not self.non_batchwise_balance_qty.get(batch_no):
self.batch_avg_rate[batch_no] = 0.0
self.stock_value_differece[batch_no] = 0.0
else:

View File

@@ -455,10 +455,14 @@ def get_available_batches(kwargs):
batches = get_auto_batch_nos(kwargs)
for batch in batches:
if batch.get("batch_no") not in batchwise_qty:
batchwise_qty[batch.get("batch_no")] = batch.get("qty")
key = batch.get("batch_no")
if kwargs.get("based_on_warehouse"):
key = (batch.get("batch_no"), batch.get("warehouse"))
if key not in batchwise_qty:
batchwise_qty[key] = batch.get("qty")
else:
batchwise_qty[batch.get("batch_no")] += batch.get("qty")
batchwise_qty[key] += batch.get("qty")
return batchwise_qty

View File

@@ -116,7 +116,6 @@ class ClosingStockBalance(Document):
"item_group": self.item_group,
"warehouse_type": self.warehouse_type,
"include_uom": self.include_uom,
"ignore_closing_balance": 1,
"show_variant_attributes": 1,
"show_stock_ageing_data": 1,
}

View File

@@ -1132,7 +1132,7 @@ def make_packing_slip(source_name, target_doc=None):
"batch_no": "batch_no",
"description": "description",
"qty": "qty",
"stock_uom": "stock_uom",
"uom": "stock_uom",
"name": "dn_detail",
},
"postprocess": update_item,

View File

@@ -756,6 +756,7 @@
"fieldtype": "Currency",
"label": "Incoming Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1,
"read_only": 1
@@ -933,7 +934,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-21 17:37:37.441498",
"modified": "2025-02-05 14:28:32.322181",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@@ -482,7 +482,7 @@ def make_request_for_quotation(source_name, target_doc=None):
"field_map": [
["name", "material_request_item"],
["parent", "material_request"],
["uom", "uom"],
["project", "project_name"],
],
},
},

View File

@@ -159,11 +159,10 @@ class PackingSlip(StatusUpdater):
self.from_case_no = self.get_recommended_case_no()
for item in self.items:
stock_uom, weight_per_unit, weight_uom = frappe.db.get_value(
"Item", item.item_code, ["stock_uom", "weight_per_unit", "weight_uom"]
weight_per_unit, weight_uom = frappe.db.get_value(
"Item", item.item_code, ["weight_per_unit", "weight_uom"]
)
item.stock_uom = stock_uom
if weight_per_unit and not item.net_weight:
item.net_weight = weight_per_unit
if weight_uom and not item.weight_uom:

View File

@@ -1360,26 +1360,25 @@ def get_item_account_wise_additional_cost(purchase_document):
for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document:
for account in landed_cost_voucher_doc.taxes:
exchange_rate = account.exchange_rate or 1
item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {})
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
)
if total_item_cost > 0:
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account
]["amount"] += account.amount * item.get(based_on_field) / total_item_cost
item_row = item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account
]
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account
]["base_amount"] += account.base_amount * item.get(based_on_field) / total_item_cost
if total_item_cost > 0:
item_row["amount"] += account.amount * item.get(based_on_field) / total_item_cost
item_row["base_amount"] += (
account.base_amount * item.get(based_on_field) / total_item_cost
)
else:
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account
]["amount"] += item.applicable_charges
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account
]["base_amount"] += item.applicable_charges
item_row["amount"] += item.applicable_charges / exchange_rate
item_row["base_amount"] += item.applicable_charges
return item_account_wise_cost

View File

@@ -7,7 +7,7 @@ frappe.ui.form.on("Serial and Batch Bundle", {
},
before_submit(frm) {
frappe.throw(__("User cannot submitted the Serial and Batch Bundle manually"));
frappe.throw(__("The user cannot submit the Serial and Batch Bundle manually"));
},
refresh(frm) {

View File

@@ -1,13 +1,12 @@
{
"actions": [],
"autoname": "naming_series:",
"autoname": "hash",
"creation": "2023-08-11 17:22:12.907518",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_details_tab",
"naming_series",
"company",
"item_name",
"has_serial_no",
@@ -152,6 +151,7 @@
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "avg_rate",
"fieldtype": "Float",
"label": "Avg Rate",
@@ -159,6 +159,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "total_amount",
"fieldtype": "Float",
"label": "Total Amount",
@@ -166,6 +167,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Qty",
@@ -195,12 +197,6 @@
"reqd": 1,
"search_index": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "SABB-.########"
},
{
"default": "0",
"depends_on": "eval:doc.voucher_type == \"Purchase Receipt\"",
@@ -251,11 +247,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-15 15:22:24.003486",
"modified": "2025-02-12 10:53:32.090309",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial and Batch Bundle",
"naming_rule": "By \"Naming Series\" field",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
@@ -389,4 +385,4 @@
"sort_order": "DESC",
"states": [],
"title_field": "item_code"
}
}

View File

@@ -55,9 +55,7 @@ class SerialandBatchBundle(Document):
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.stock.doctype.serial_and_batch_entry.serial_and_batch_entry import (
SerialandBatchEntry,
)
from erpnext.stock.doctype.serial_and_batch_entry.serial_and_batch_entry import SerialandBatchEntry
amended_from: DF.Link | None
avg_rate: DF.Float
@@ -70,7 +68,6 @@ class SerialandBatchBundle(Document):
item_code: DF.Link
item_group: DF.Link | None
item_name: DF.Data | None
naming_series: DF.Literal["SABB-.########"]
posting_date: DF.Date | None
posting_time: DF.Time | None
returned_against: DF.Data | None
@@ -84,6 +81,9 @@ class SerialandBatchBundle(Document):
# end: auto-generated types
def validate(self):
if self.docstatus == 1 and self.voucher_detail_no:
self.validate_voucher_detail_no()
self.reset_serial_batch_bundle()
self.set_batch_no()
self.validate_serial_and_batch_no()
@@ -101,9 +101,36 @@ class SerialandBatchBundle(Document):
self.set_is_outward()
self.calculate_total_qty()
self.set_warehouse()
self.set_incoming_rate()
if self.voucher_type != "Stock Entry" or not self.voucher_no or self.docstatus == 1:
self.set_incoming_rate()
self.calculate_qty_and_amount()
def validate_voucher_detail_no(self):
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
"Installation Note",
"Job Card",
"Maintenance Schedule",
"Pick List",
]:
return
if self.voucher_type == "POS Invoice":
if not frappe.db.exists("POS Invoice Item", self.voucher_detail_no):
frappe.throw(
_("The serial and batch bundle {0} not linked to {1} {2}").format(
bold(self.name), self.voucher_type, bold(self.voucher_no)
)
)
elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}):
frappe.throw(
_("The serial and batch bundle {0} not linked to {1} {2}").format(
bold(self.name), self.voucher_type, bold(self.voucher_no)
)
)
def allow_existing_serial_nos(self):
if self.type_of_transaction == "Outward" or not self.has_serial_no:
return
@@ -1026,7 +1053,6 @@ class SerialandBatchBundle(Document):
self.set_purchase_document_no()
def on_submit(self):
self.validate_batch_inventory()
self.validate_serial_nos_inventory()
def set_purchase_document_no(self):
@@ -1053,25 +1079,9 @@ class SerialandBatchBundle(Document):
self.validate_batch_inventory()
def validate_batch_inventory(self):
if (
self.voucher_type in ["Purchase Invoice", "Purchase Receipt"]
and frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1
):
return
if self.voucher_type in ["Sales Invoice", "Delivery Note"] and self.type_of_transaction == "Inward":
return
if not self.has_batch_no:
return
if (
self.voucher_type == "Stock Reconciliation"
and self.type_of_transaction == "Outward"
and frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty") > 0
):
return
batches = [d.batch_no for d in self.entries if d.batch_no]
if not batches:
return
@@ -2440,6 +2450,9 @@ def get_stock_ledgers_batches(kwargs):
else:
query = query.where(stock_ledger_entry[field] == kwargs.get(field))
if not kwargs.get("for_stock_levels"):
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
if kwargs.get("posting_date"):
if kwargs.get("posting_time") is None:
kwargs.posting_time = nowtime()

View File

@@ -755,6 +755,80 @@ class TestSerialandBatchBundle(FrappeTestCase):
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", original_value
)
def test_voucher_detail_no(self):
item_code = make_item(
"Test Voucher Detail No 1",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TST-VDN-.#####",
},
).name
se = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=500,
use_serial_batch_fields=True,
do_not_submit=True,
)
if not frappe.db.exists("Batch", "TST-ACSBBO-TACSB-00001"):
frappe.get_doc(
{
"doctype": "Batch",
"batch_id": "TST-ACSBBO-TACSB-00001",
"item": item_code,
"company": "_Test Company",
}
).insert(ignore_permissions=True)
bundle_doc = make_serial_batch_bundle(
{
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
"voucher_type": "Stock Entry",
"posting_date": today(),
"posting_time": nowtime(),
"qty": 10,
"batches": frappe._dict({"TST-ACSBBO-TACSB-00001": 10}),
"type_of_transaction": "Inward",
"do_not_submit": True,
}
)
se.append(
"items",
{
"item_code": item_code,
"t_warehouse": "_Test Warehouse - _TC",
"stock_uom": "Nos",
"stock_qty": 10,
"conversion_factor": 1,
"uom": "Nos",
"basic_rate": 500,
"qty": 10,
"use_serial_batch_fields": 0,
"serial_and_batch_bundle": bundle_doc.name,
},
)
se.save()
bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle_doc.name)
self.assertEqual(bundle_doc.voucher_detail_no, se.items[1].name)
se.remove(se.items[1])
se.save()
self.assertTrue(len(se.items) == 1)
se.submit()
bundle_doc.reload()
self.assertTrue(bundle_doc.docstatus == 0)
self.assertRaises(frappe.ValidationError, bundle_doc.submit)
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos

View File

@@ -295,6 +295,11 @@ frappe.ui.form.on("Stock Reconciliation Item", {
qty: function (frm, cdt, cdn) {
frm.events.set_amount_quantity(frm, cdt, cdn);
let row = locals[cdt][cdn];
if (row.use_serial_batch_fields && !row.qty && row.serial_no) {
frappe.model.set_value(cdt, cdn, "serial_no", "");
}
},
valuation_rate: function (frm, cdt, cdn) {

View File

@@ -1372,13 +1372,13 @@ def get_stock_balance_for(
or 0
)
if row.use_serial_batch_fields and row.batch_no:
if row.use_serial_batch_fields and row.batch_no and (qty or row.current_qty):
rate = get_incoming_rate(
frappe._dict(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"qty": row.qty * -1,
"qty": flt(qty or row.current_qty) * -1,
"batch_no": row.batch_no,
"company": company,
"posting_date": posting_date,

View File

@@ -1408,6 +1408,44 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
self.assertFalse(sr.items[0].serial_and_batch_bundle)
def test_stock_reco_batch_item_current_valuation(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
# Add new serial nos
item_code = "Stock-Reco-batch-Item-1234"
warehouse = "_Test Warehouse - _TC"
self.make_item(
item_code,
frappe._dict(
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "JJ-SRI1234-.#####",
}
),
)
se = make_stock_entry(
item_code=item_code,
target=warehouse,
qty=1,
basic_rate=100,
)
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
sr = create_stock_reconciliation(
item_code=item_code, warehouse=warehouse, qty=0, rate=100, do_not_save=1
)
sr.items[0].batch_no = batch_no
sr.items[0].use_serial_batch_fields = 1
sr.save()
self.assertEqual(sr.items[0].current_valuation_rate, 100)
self.assertEqual(sr.difference_amount, 100 * -1)
self.assertTrue(sr.items[0].qty == 0)
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)

View File

@@ -187,6 +187,9 @@ def update_stock(ctx, out, doc=None):
and out.warehouse
and out.stock_qty > 0
):
if doc and isinstance(doc, dict):
doc = frappe._dict(doc)
kwargs = frappe._dict(
{
"item_code": ctx.item_code,
@@ -206,13 +209,21 @@ def update_stock(ctx, out, doc=None):
filter_batches(batches, doc)
for batch_no, batch_qty in batches.items():
rate = get_batch_based_item_price(
{"price_list": doc.get("selling_price_list"), "uom": out.uom, "batch_no": batch_no},
out.item_code,
)
if batch_qty >= qty:
out.update({"batch_no": batch_no, "actual_batch_qty": qty})
if rate:
out.update({"rate": rate, "price_list_rate": rate})
break
else:
qty -= batch_qty
out.update({"batch_no": batch_no, "actual_batch_qty": batch_qty})
out.update({"batch_no": batch_no, "actual_batch_qty": qty})
if rate:
out.update({"rate": rate, "price_list_rate": rate})
if out.has_serial_no and out.has_batch_no and has_incorrect_serial_nos(ctx, out):
kwargs["batches"] = [ctx.get("batch_no")] if ctx.get("batch_no") else [out.get("batch_no")]

View File

@@ -151,6 +151,8 @@ def get_stock_ledger_entries_for_batch_bundle(filters):
sle = frappe.qb.DocType("Stock Ledger Entry")
batch_package = frappe.qb.DocType("Serial and Batch Entry")
to_date = get_datetime(filters.to_date + " 23:59:59")
query = (
frappe.qb.from_(sle)
.inner_join(batch_package)
@@ -166,7 +168,7 @@ def get_stock_ledger_entries_for_batch_bundle(filters):
(sle.docstatus < 2)
& (sle.is_cancelled == 0)
& (sle.has_batch_no == 1)
& (sle.posting_date <= filters["to_date"])
& (sle.posting_datetime <= to_date)
)
.groupby(sle.voucher_no, batch_package.batch_no, batch_package.warehouse)
.orderby(sle.item_code, sle.warehouse)

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["Incorrect Serial and Batch Bundle"] = {
filters: [
{
fieldname: "item_code",
label: __("Item Code"),
fieldtype: "Link",
options: "Item",
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
},
],
get_datatable_options(options) {
return Object.assign(options, {
checkboxColumn: true,
});
},
onload(report) {
report.page.add_inner_button(__("Remove SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
if (!selected_rows.length) {
frappe.throw(__("Please select a row to create a Reposting Entry"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
});
},
};

View File

@@ -0,0 +1,56 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2025-02-03 15:39:44.521366",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letter_head": "Test",
"letterhead": null,
"modified": "2025-02-03 15:39:47.613040",
"modified_by": "Administrator",
"module": "Stock",
"name": "Incorrect Serial and Batch Bundle",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Serial and Batch Bundle",
"report_name": "Incorrect Serial and Batch Bundle",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Delivery Manager"
},
{
"role": "System Manager"
},
{
"role": "Delivery User"
},
{
"role": "Manufacturing User"
},
{
"role": "Purchase User"
},
{
"role": "Stock Manager"
},
{
"role": "Manufacturing Manager"
},
{
"role": "Maintenance User"
}
],
"timeout": 0
}

View File

@@ -0,0 +1,107 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
def execute(filters: dict | None = None):
"""Return columns and data for the report.
This is the main entry point for the report. It accepts the filters as a
dictionary and should return columns and data. It is called by the framework
every time the report is refreshed or a filter is updated.
"""
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns() -> list[dict]:
"""Return columns for the report.
One field definition per column, just like a DocType field definition.
"""
return [
{
"label": _("Serial and Batch Bundle"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Serial and Batch Bundle",
"width": 200,
},
{
"label": _("Voucher Type"),
"fieldname": "voucher_type",
"fieldtype": "Data",
"width": 200,
},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
"width": 200,
},
{
"label": _("Voucher Detail No"),
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"width": 200,
},
]
def get_data(filters) -> list[list]:
"""Return data for the report.
The report data is a list of rows, with each row being a list of cell values.
"""
SABB = frappe.qb.DocType("Serial And Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
ignore_voycher_types = [
"Installation Note",
"Job Card",
"Maintenance Schedule",
"Pick List",
]
query = (
frappe.qb.from_(SABB)
.left_join(SLE)
.on(SABB.name == SLE.serial_and_batch_bundle)
.select(
SABB.name,
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
)
.where(
(SLE.serial_and_batch_bundle.isnull())
& (SABB.docstatus == 1)
& (SABB.is_cancelled == 0)
& (SABB.voucher_type.notin(ignore_voycher_types))
)
)
for field in filters:
query = query.where(SABB[field] == filters[field])
data = query.run(as_dict=1)
return data
@frappe.whitelist()
def remove_sabb_entry(selected_rows):
if isinstance(selected_rows, str):
selected_rows = frappe.parse_json(selected_rows)
for row in selected_rows:
doc = frappe.get_doc("Serial and Batch Bundle", row.get("name"))
doc.cancel()
doc.delete()
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))

View File

@@ -7,7 +7,7 @@ from operator import itemgetter
import frappe
from frappe import _
from frappe.utils import cint, date_diff, flt
from frappe.utils import cint, date_diff, flt, get_datetime
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -424,6 +424,7 @@ class FIFOSlots:
def __get_stock_ledger_entries(self) -> Iterator[dict]:
sle = frappe.qb.DocType("Stock Ledger Entry")
item = self.__get_item_query() # used as derived table in sle query
to_date = get_datetime(self.filters.get("to_date") + " 23:59:59")
sle_query = (
frappe.qb.from_(sle)
@@ -450,7 +451,7 @@ class FIFOSlots:
.where(
(sle.item_code == item.name)
& (sle.company == self.filters.get("company"))
& (sle.posting_date <= self.filters.get("to_date"))
& (sle.posting_datetime <= to_date)
& (sle.is_cancelled != 1)
)
)
@@ -467,7 +468,7 @@ class FIFOSlots:
if warehouses:
sle_query = sle_query.where(sle.warehouse.isin(warehouses))
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
sle_query = sle_query.orderby(sle.posting_datetime, sle.creation)
return sle_query.run(as_dict=True, as_iterator=True)

View File

@@ -8,7 +8,7 @@ from collections import defaultdict
import frappe
from frappe import _
from frappe.query_builder.functions import CombineDatetime, Sum
from frappe.utils import cint, flt
from frappe.utils import cint, flt, get_datetime
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -367,6 +367,9 @@ def get_columns(filters):
def get_stock_ledger_entries(filters, items):
from_date = get_datetime(filters.from_date + " 00:00:00")
to_date = get_datetime(filters.to_date + " 23:59:59")
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
@@ -390,12 +393,8 @@ def get_stock_ledger_entries(filters, items):
sle.serial_no,
sle.project,
)
.where(
(sle.docstatus < 2)
& (sle.is_cancelled == 0)
& (sle.posting_date[filters.from_date : filters.to_date])
)
.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
.where((sle.docstatus < 2) & (sle.is_cancelled == 0) & (sle.posting_datetime[from_date:to_date]))
.orderby(sle.posting_datetime)
.orderby(sle.creation)
)

View File

@@ -302,9 +302,6 @@ class SerialBatchBundle:
):
self.set_batch_no_in_serial_nos()
if self.item_details.has_batch_no == 1:
self.update_batch_qty()
if self.sle.is_cancelled and self.sle.serial_and_batch_bundle:
self.cancel_serial_and_batch_bundle()
@@ -410,26 +407,6 @@ class SerialBatchBundle:
.where(sn_table.name.isin(serial_nos))
).run()
def update_batch_qty(self):
from erpnext.stock.doctype.batch.batch import get_available_batches
batches = get_batch_nos(self.sle.serial_and_batch_bundle)
if not self.sle.serial_and_batch_bundle and self.sle.batch_no:
batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
batches_qty = get_available_batches(
frappe._dict(
{
"item_code": self.item_code,
"batch_no": list(batches.keys()),
"consider_negative_batches": 1,
}
)
)
for batch_no in batches:
frappe.db.set_value("Batch", batch_no, "batch_qty", batches_qty.get(batch_no, 0))
def get_serial_nos(serial_and_batch_bundle, serial_nos=None):
if not serial_and_batch_bundle:
@@ -657,7 +634,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
parent = frappe.qb.DocType("Serial and Batch Bundle")
child = frappe.qb.DocType("Serial and Batch Entry")
sle = frappe.qb.DocType("Stock Ledger Entry")
timestamp_condition = ""
if self.sle.posting_date:
@@ -690,14 +666,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
& (parent.docstatus == 1)
& (parent.is_cancelled == 0)
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
& (
ExistsCriterion(
frappe.qb.from_(sle)
.select(sle.name)
.where((parent.name == sle.serial_and_batch_bundle) & (sle.is_cancelled == 0))
)
| (parent.voucher_type == "POS Invoice")
)
)
.groupby(child.batch_no)
)
@@ -1112,6 +1080,7 @@ class SerialBatchCreation:
def set_serial_batch_entries(self, doc):
incoming_rate = self.get("incoming_rate")
precision = frappe.get_precision("Serial and Batch Entry", "qty")
if self.get("serial_nos"):
serial_no_wise_batch = frappe._dict({})
if self.has_batch_no:
@@ -1141,7 +1110,8 @@ class SerialBatchCreation:
"entries",
{
"batch_no": batch_no,
"qty": batch_qty * (-1 if self.type_of_transaction == "Outward" else 1),
"qty": flt(batch_qty, precision)
* (-1 if self.type_of_transaction == "Outward" else 1),
"incoming_rate": incoming_rate,
},
)
@@ -1258,3 +1228,53 @@ def get_serial_nos_batch(serial_nos):
as_list=1,
)
)
def update_batch_qty(voucher_type, voucher_no, via_landed_cost_voucher=False):
from erpnext.stock.doctype.batch.batch import get_available_batches
batches = get_distinct_batches(voucher_type, voucher_no)
if not batches:
return
precision = frappe.get_precision("Batch", "batch_qty")
batch_data = get_available_batches(
frappe._dict({"batch_no": batches, "consider_negative_batches": 1, "based_on_warehouse": True})
)
batchwise_qty = defaultdict(float)
for (batch_no, warehouse), qty in batch_data.items():
if not via_landed_cost_voucher and flt(qty, precision) < 0:
throw_negative_batch_validation(batch_no, warehouse, qty)
batchwise_qty[batch_no] += qty
for batch_no in batches:
qty = flt(batchwise_qty.get(batch_no, 0), precision)
frappe.db.set_value("Batch", batch_no, "batch_qty", qty)
def throw_negative_batch_validation(batch_no, warehouse, qty):
frappe.throw(
_("The Batch {0} has negative quantity {1} in warehouse {2}. Please correct the quantity.").format(
bold(batch_no), bold(qty), bold(warehouse)
),
title=_("Negative Batch Quantity"),
)
def get_distinct_batches(voucher_type, voucher_no):
bundles = frappe.get_all(
"Serial and Batch Bundle",
filters={"voucher_no": voucher_no, "voucher_type": voucher_type},
pluck="name",
)
if not bundles:
return
return frappe.get_all(
"Serial and Batch Entry",
filters={"parent": ("in", bundles), "batch_no": ("is", "set")},
group_by="batch_no",
pluck="batch_no",
)

View File

@@ -405,23 +405,13 @@ def create_json_gz_file(data, doc, file_name=None) -> str:
compressed_content = gzip.compress(encoded_content)
if not file_name:
json_filename = f"{scrub(doc.doctype)}-{scrub(doc.name)}.json.gz"
_file = frappe.get_doc(
{
"doctype": "File",
"file_name": json_filename,
"attached_to_doctype": doc.doctype,
"attached_to_name": doc.name,
"attached_to_field": "reposting_data_file",
"content": compressed_content,
"is_private": 1,
}
)
_file.save(ignore_permissions=True)
return _file.file_url
return create_file(doc, compressed_content)
else:
file_doc = frappe.get_doc("File", file_name)
if "/frappe_s3_attachment." in file_doc.file_url:
file_doc.delete()
return create_file(doc, compressed_content)
path = file_doc.get_full_path()
with open(path, "wb") as f:
@@ -430,6 +420,24 @@ def create_json_gz_file(data, doc, file_name=None) -> str:
return doc.reposting_data_file
def create_file(doc, compressed_content):
json_filename = f"{scrub(doc.doctype)}-{scrub(doc.name)}.json.gz"
_file = frappe.get_doc(
{
"doctype": "File",
"file_name": json_filename,
"attached_to_doctype": doc.doctype,
"attached_to_name": doc.name,
"attached_to_field": "reposting_data_file",
"content": compressed_content,
"is_private": 1,
}
)
_file.save(ignore_permissions=True)
return _file.file_url
def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None, reposting_data=None):
if not reposting_data and doc and doc.reposting_data_file:
reposting_data = get_reposting_data(doc.reposting_data_file)

View File

@@ -34,3 +34,66 @@ class TestGetItemDetail(FrappeTestCase):
)
details = get_item_details(args)
self.assertEqual(details.get("price_list_rate"), 100)
# making this test in get_item_details test file as feat/fix is present in that method
def test_fetch_price_from_list_rate_on_doc_save(self):
# create item
item = frappe.get_doc(
{
"doctype": "Item",
"item_code": "Test Item with Batch",
"item_name": "Test Item with Batch",
"item_group": "All Item Groups",
"is_stock_item": 1,
"has_batch_no": 1,
}
).insert()
# create batch
frappe.get_doc(
{
"doctype": "Batch",
"batch_id": "BATCH01",
"item": item,
}
).insert()
# create item price
frappe.get_doc(
{
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 50,
"batch_no": "BATCH01",
}
).insert()
# create purchase receipt to have some stock for delivery
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
make_purchase_receipt(
item_code=item.item_code,
warehouse="_Test Warehouse - _TC",
qty=100,
rate=100,
batch_no="BATCH01",
)
# creating sales order just to create delivery note from it
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
so = make_sales_order(item_code=item.item_code, qty=2, rate=75)
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
dn = make_delivery_note(so.name)
# Test 1 : On creation of DN, item's batch won't be fetched and rate will remaing the same as in SO
self.assertIsNone(dn.items[0].batch_no)
self.assertEqual(dn.items[0].rate, 75)
# Test 2 : On saving the DN, item's batch will be fetched and rate will be updated from Item Price
dn.save()
self.assertEqual(dn.items[0].batch_no, "BATCH01")
self.assertEqual(dn.items[0].rate, 50)

View File

@@ -40,7 +40,7 @@
<p>
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay", null, "Amount") }} {{ pay_amount }}
{{ _("Pay", null, "Amount") }} {{doc.get_formatted("grand_total") }}
</a>
</p>
</div>
@@ -72,8 +72,7 @@
</span>
</div>
<div class="text-right col-2">
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase
Order'] else doc.customer_name %}
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %}
<b>{{ party_name }}</b>
{% if doc.contact_display and doc.contact_display != party_name %}