Compare commits

..

184 Commits

Author SHA1 Message Date
marination
7e5cf18066 fix: Wrong bank_ac_no filter + simplify convoluted logic 2025-01-07 13:08:07 +05:30
mergify[bot]
01254da4e0 fix: update customer contact details on pos (backport #45071) (#45106)
fix: update customer contact details on pos (#45071)

* fix: update customer contact details on pos

* refactor: removed console log statement

(cherry picked from commit d79e561248)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-06 15:01:23 +05:30
rohitwaghchaure
dadc8266dc Merge pull request #45098 from frappe/mergify/bp/version-15-hotfix/pr-45084
fix: Alternative Items button in Work Order (backport #45084)
2025-01-06 14:48:02 +05:30
ruthra kumar
100b4e9274 Merge pull request #45103 from frappe/mergify/bp/version-15-hotfix/pr-45006
fix: pos search by term items price (backport #45006)
2025-01-06 14:43:56 +05:30
diptanilsaha
59af144e29 fix: load search term price with customer default price list
(cherry picked from commit 2beb485d77)
2025-01-06 08:39:58 +00:00
diptanilsaha
452dffab48 fix: load price list rate for pos search term
(cherry picked from commit 4b6cae156e)
2025-01-06 08:39:57 +00:00
ruthra kumar
eff12cbfbe Merge pull request #45101 from frappe/mergify/bp/version-15-hotfix/pr-45099
fix: show new button in coa if create access (backport #45099)
2025-01-06 13:56:43 +05:30
sokumon
89155f529e fix: show new button in coa if create access
(cherry picked from commit 3125bc8a16)
2025-01-06 08:20:01 +00:00
Mihir Kandoi
642b89782d fix: Alternative Item button dissapearing on Save event
(cherry picked from commit b8838bd9b9)
2025-01-06 06:52:16 +00:00
Mihir Kandoi
3194807a41 fix: Alternative Items button in Work Order
(cherry picked from commit e28382afc1)
2025-01-06 06:52:16 +00:00
ruthra kumar
2333d33362 Merge pull request #45095 from frappe/mergify/bp/version-15-hotfix/pr-44943
fix: Bank Reconciliation Statement Report Company Filter (backport #44943)
2025-01-06 10:51:48 +05:30
creative-paramu
622bfa6633 fix: Bank Reconciliation Statement Report Company Filter
(cherry picked from commit 50c92034ba)
2025-01-06 10:30:24 +05:30
ruthra kumar
e22771c729 Merge pull request #45080 from frappe/mergify/bp/version-15-hotfix/pr-44983
fix: add monthly distributation and write query in qb (backport #44983)
2025-01-06 10:28:18 +05:30
ruthra kumar
5b066f4a59 Merge pull request #45096 from frappe/mergify/bp/version-15-hotfix/pr-44952
fix: Hold_to_On_Hold (backport #44952)
2025-01-06 10:09:35 +05:30
mahsem
9ecafdc680 fix: Hold_to_On_Hold
(cherry picked from commit 92b1f314ef)
2025-01-06 03:15:30 +00:00
ruthra kumar
05763d226a Merge pull request #45093 from frappe/mergify/bp/version-15-hotfix/pr-45051
fix: ignore currency validation while canceling the voucher (backport #45051)
2025-01-06 08:44:46 +05:30
venkat102
0f1c6ff1c9 fix: ignore party account validation while canceling the voucher
(cherry picked from commit 49885f8eae)
2025-01-06 02:55:50 +00:00
venkat102
8874f4a9e4 fix: ignore currency validation while canceling the voucher
(cherry picked from commit 15d488b9aa)
2025-01-06 02:55:49 +00:00
rohitwaghchaure
21a83c508a Merge pull request #45089 from frappe/mergify/bp/version-15-hotfix/pr-45087
fix: invoice against purchase receipt with returned quantity (backport #45087)
2025-01-05 19:02:07 +05:30
rohitwaghchaure
90b8860a40 chore: fix conflicts 2025-01-05 16:24:15 +05:30
Rohit Waghchaure
9daabfca8a fix: invoice against purchase receipt with returned quantity
(cherry picked from commit d5babf4237)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
2025-01-04 15:08:10 +00:00
rohitwaghchaure
10a4b54a67 Merge pull request #45086 from frappe/mergify/bp/version-15-hotfix/pr-45083
fix: consider expired batches in stock reco (backport #45083)
2025-01-04 20:37:16 +05:30
Rohit Waghchaure
c924feb0d0 fix: consider expired batches in stock reco
(cherry picked from commit f51c9f578c)
2025-01-04 13:57:04 +00:00
Joseph Mania
cfa062df86 refactor: phone number field when channel is phone on Payment Request (#44949)
Co-authored-by: maniamartial <martialamania19@gmail.com>
2025-01-04 05:23:41 +05:30
ruthra kumar
2e67a33412 refactor: store result in variable before enumeration
helps to inspect result while debugging

(cherry picked from commit b60bd17d1d)
2025-01-03 23:48:40 +00:00
Sanket322
03b06fc3ff fix: add monthly distributation and write query in qb
(cherry picked from commit 27195c7c96)
2025-01-03 23:48:40 +00:00
mergify[bot]
500deff3e9 feat: validate discount date in payment schedule (backport #44646) (#44727)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-01-03 13:19:34 +01:00
ruthra kumar
2c487af2df Merge pull request #45058 from frappe/mergify/bp/version-15-hotfix/pr-45056
chore: partial revert #44989 (backport #45056)
2025-01-02 21:04:22 +05:30
ruthra kumar
4dfc5a664a chore: partial revert #44989
(cherry picked from commit 63d547fb4a)
2025-01-02 15:27:40 +00:00
rohitwaghchaure
d3ea8b8e77 Merge pull request #45054 from frappe/mergify/bp/version-15-hotfix/pr-45053
fix(style): set image width in BOM (backport #45053)
2025-01-02 19:07:29 +05:30
sokumon
6247d5aadb fix(style): set image width in BOM
(cherry picked from commit b634ba7f54)
2025-01-02 13:31:21 +00:00
rohitwaghchaure
85167bf934 Merge pull request #45044 from frappe/mergify/bp/version-15-hotfix/pr-45043
fix: validate components and their qty as per BOM in the stock entry (backport #45043)
2025-01-02 14:19:38 +05:30
Rohit Waghchaure
b5f6926140 fix: validate components and their qty as per BOM in the stock entry
(cherry picked from commit b1de82ddad)
2025-01-02 08:16:03 +00:00
rohitwaghchaure
c615df5ac4 Merge pull request #45042 from frappe/mergify/bp/version-15-hotfix/pr-45039
fix: removed unused code (backport #45039)
2025-01-02 12:36:25 +05:30
rohitwaghchaure
d26d0c6282 Merge pull request #45031 from frappe/mergify/bp/version-15-hotfix/pr-44999
fix: Added patch and fallback code to prevent future issues similar … (backport #44999)
2025-01-02 12:18:18 +05:30
Rohit Waghchaure
d31b0a507f fix: removed unused code
(cherry picked from commit dc5f2d35ac)
2025-01-02 06:47:38 +00:00
rohitwaghchaure
bd12c1475a Merge pull request #45038 from frappe/mergify/bp/version-15-hotfix/pr-45036
fix: Auto BOM cost update issue (backport #45036)
2025-01-02 12:15:50 +05:30
Mihir Kandoi
f9d038ee4a fix: removed unknown patch? 2025-01-02 10:51:11 +05:30
Rohit Waghchaure
00102a15e3 fix: BOM cost update issue
(cherry picked from commit 28ea3ddd51)
2025-01-02 04:55:22 +00:00
Mihir Kandoi
3049027f43 fix: Removed patch as instructed by mentor 2025-01-01 17:13:49 +05:30
Mihir Kandoi
ab87265395 fix: Added patch and fallback code to prevent future issues similiar to helpdesk ticket 28246 2025-01-01 17:12:10 +05:30
Mihir Kandoi
163af91c37 fix: Removed patch as instructed by mentor
(cherry picked from commit d1d01482df)

# Conflicts:
#	erpnext/patches.txt
2025-01-01 11:06:27 +00:00
Mihir Kandoi
b3b808335f fix: Fixed logic in if condition causing tests to fail
(cherry picked from commit 575fb43f9c)
2025-01-01 11:06:27 +00:00
Mihir Kandoi
66544bfa10 fix: Added patch and fallback code to prevent future issues similiar to helpdesk ticket 28246
(cherry picked from commit 65dc3505c4)

# Conflicts:
#	erpnext/patches.txt
2025-01-01 11:06:27 +00:00
mergify[bot]
85ba96e0f3 fix: slow stock transactions (backport #45025) (#45027)
fix: slow stock transactions (#45025)

(cherry picked from commit e92af10f14)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-01-01 15:11:01 +05:30
ruthra kumar
1a1476afa4 Merge pull request #45008 from frappe/mergify/bp/version-15-hotfix/pr-44884
fix: update item_tax_rate in backend (backport #44884)
2025-01-01 13:48:50 +05:30
ruthra kumar
e7f4a9bf77 Merge pull request #45023 from frappe/mergify/bp/version-15-hotfix/pr-44989
fix: apply apply_pricing_rule on date change (backport #44989)
2025-01-01 10:19:11 +05:30
DHINESH00
f09acc784f fix: apply apply_pricing_rule date change
(cherry picked from commit 2cbab9b875)
2025-01-01 04:42:48 +00:00
mergify[bot]
0a2cc6bcd7 fix: incorrect quality inspection linked in purchase receipt (backport #44985) (#45020)
* fix: incorrect quality inspection linked in purchase receipt (#44985)

(cherry picked from commit b84c8ff960)

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

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-01-01 09:24:48 +05:30
mergify[bot]
52bdf5b170 fix: precision issue (backport #45013) (#45019)
fix: precision issue (#45013)

(cherry picked from commit 7db9bcaeac)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-01-01 09:08:16 +05:30
rohitwaghchaure
8d650e56ba fix: duplicate validate for closing stock balance (#45015) 2025-01-01 08:29:02 +05:30
ruthra kumar
e09fb87597 refactor: use existing method parameters 2024-12-31 17:12:34 +05:30
ruthra kumar
a11aee3ab3 Merge pull request #44936 from frappe/mergify/bp/version-15-hotfix/pr-44904
fix: ignore duplicate while creating default templates (backport #44904)
2024-12-31 17:01:03 +05:30
ruthra kumar
92ad2ce554 Merge pull request #44979 from frappe/mergify/bp/version-15-hotfix/pr-44855
fix: in_context_translation_fixes (backport #44855)
2024-12-31 16:59:05 +05:30
ruthra kumar
3533c25969 Merge pull request #45011 from frappe/mergify/bp/version-15-hotfix/pr-44940
fix(Bank Reconciliation Tool): fetch amount in company  currency (backport #44940)
2024-12-31 16:22:00 +05:30
ruthra kumar
aff83051a6 Merge pull request #45010 from frappe/mergify/bp/version-15-hotfix/pr-44903
fix: set paid amount in party currency in bank reco payment entry (backport #44903)
2024-12-31 16:16:16 +05:30
DHINESH00
b135a684a5 fix: fetch amount in company currency
(cherry picked from commit a984aaae36)
2024-12-31 10:28:46 +00:00
ruthra kumar
885dd31c5c chore: resolve conflict 2024-12-31 15:54:06 +05:30
ljain112
6703a457fe fix: set paid amount in party currency in bank reco payment entry
(cherry picked from commit 70b1077286)
2024-12-31 10:23:42 +00:00
ljain112
8885b07114 fix: update item_tax_rate in backend
(cherry picked from commit de54c0b41f)

# Conflicts:
#	erpnext/controllers/taxes_and_totals.py
2024-12-31 10:21:39 +00:00
mergify[bot]
6f7138996a fix: negative stock balance (backport #44990) (#44996)
* fix: negative stock balance (#44990)

(cherry picked from commit 7c4aecf834)

# Conflicts:
#	erpnext/stock/deprecated_serial_batch.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-31 13:17:18 +05:30
Khushi Rawat
8d5fe20c7c Merge pull request #44997 from frappe/mergify/bp/version-15-hotfix/pr-44964
fix: copy accounting dimensions to asset and sales invoice (backport #44964)
2024-12-31 13:13:15 +05:30
Khushi Rawat
b8922823a3 fix: remove unused variable 2024-12-31 12:55:41 +05:30
mergify[bot]
20efe7bb80 fix: load customer default price list in pos during item selection (backport #44991) (#44993)
fix: load customer default price list in pos during item selection (#44991)

fix: load customer default price list in pos
(cherry picked from commit d1ae0d784e)

Co-authored-by: Diptanil Saha <diptanil.dev@gmail.com>
2024-12-31 12:52:23 +05:30
mergify[bot]
ea4b6e8dd7 fix(report): Purchase Order Analysis pymysql.err (backport #44957) (#44994)
fix(report): Purchase Order Analysis pymysql.err (#44957)

(cherry picked from commit d6980a9493)

Co-authored-by: Vishnu  VS <Vishnuviswambaran2002@gmail.com>
2024-12-31 12:52:13 +05:30
Khushi Rawat
00ae829d89 fix: resolved conflicts 2024-12-31 12:43:38 +05:30
Khushi Rawat
839ffb3f2a fix: copy accounting dimensions to asset and sales invoice (#44964)
* fix: copy accounting dimensions to asset and sales invoice

* fix: replace sql query with query builder

* refactor: reuse function for accounting dimensions

* fix: loop handling

* fix: use explicit param

(cherry picked from commit 079ec864de)

# Conflicts:
#	erpnext/controllers/buying_controller.py
2024-12-31 07:07:16 +00:00
mergify[bot]
d6903fbc8d fix: Validate party on non receivable / payable account (backport #44883) (#44973)
* fix: validate party on non receivable / payable account

(cherry picked from commit c6a2d86ba6)

* test: add unit test to validate on non receivable / payable account

(cherry picked from commit a10a15b2c3)

* fix: Set account type payable for advance account

(cherry picked from commit 8abbece7c4)

---------

Co-authored-by: Karuppasamy923 <karuppasamylivak@gmail.com>
2024-12-31 12:09:33 +05:30
mergify[bot]
9853bd9ba1 fix: incorrect filter for BOM (backport #44954) (#44956)
fix: incorrect filter for BOM (#44954)

(cherry picked from commit 9fdeb5f826)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-30 23:38:49 +05:30
mergify[bot]
c33e07550c fix: ignore validate while making WO from MR (backport #44939) (#44942)
fix: ignore validate while making WO from MR (#44939)

(cherry picked from commit 9661c1d081)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-30 23:38:14 +05:30
mergify[bot]
772b7b95ac fix: ignore inventory dimension for SABB and Pick List (backport #44933) (#44941)
fix: ignore inventory dimension for SABB and Pick List (#44933)

(cherry picked from commit 303c52f134)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-30 23:37:52 +05:30
ruthra kumar
2e9c507dfa chore: resolve conflicts 2024-12-30 16:18:18 +05:30
ruthra kumar
e3dc5d0de4 Merge pull request #44897 from frappe/mergify/bp/version-15-hotfix/pr-44787
fix: refactor query in`get_total_allocated_amount` in bank_transaction (backport #44787)
2024-12-30 16:16:15 +05:30
mahsem
2efc701e4e fix: whitespace
(cherry picked from commit 1f4e1811de)
2024-12-30 10:27:17 +00:00
mahsem
1353a14a6b fix: in_contex_translation_fixes
(cherry picked from commit a87e7fde03)

# Conflicts:
#	erpnext/accounts/utils.py
#	erpnext/templates/pages/order.html
2024-12-30 10:27:17 +00:00
ruthra kumar
8b4f5261b4 Merge pull request #44978 from frappe/mergify/bp/version-15-hotfix/pr-44975
fix: add company filter to project (backport #44975)
2024-12-30 15:55:00 +05:30
venkat102
74220430e5 fix: include company in filter condition
(cherry picked from commit b92f8bc514)
2024-12-30 09:59:09 +00:00
venkat102
d550b433c1 fix: add company filter to project
(cherry picked from commit 1a7b09e576)
2024-12-30 09:59:09 +00:00
ruthra kumar
8e55d5cc39 Merge pull request #44974 from frappe/mergify/bp/version-15-hotfix/pr-44552
Fix: Added Order Number and Order Date fields to the Blanket Order form. (backport #44552)
2024-12-30 15:27:50 +05:30
ruthra kumar
4a74ee7b6e Merge pull request #44977 from frappe/mergify/bp/version-15-hotfix/pr-44761
fix: apply discount on qty change (backport #44761)
2024-12-30 15:04:22 +05:30
DHINESH00
c19725ca74 fix: apply discount on qty change
(cherry picked from commit 352b82bc0b)
2024-12-30 09:27:31 +00:00
ruthra kumar
7b90742409 chore: resolve conflict 2024-12-30 14:50:23 +05:30
Himanshu Shivhare
583182180a refactor: Order Number and Order Date fields to Blanket Order
(cherry picked from commit 5a284df51d)

# Conflicts:
#	erpnext/manufacturing/doctype/blanket_order/blanket_order.json
2024-12-30 08:32:49 +00:00
mergify[bot]
28442f3414 fix: limit discount value to 100 in pos cart (backport #44916) (#44932)
fix: limit discount value to 100 in pos cart (#44916)

* fix: limit discount value to 100 in pos cart

* fix: error message on invalid discount

(cherry picked from commit ac26622d6e)

Co-authored-by: Diptanil Saha <50792171+diptanilsaha@users.noreply.github.com>
2024-12-30 14:00:08 +05:30
mergify[bot]
cc827c8077 fix: fetch advance payment entries on pos invoice (backport #44856) (#44931)
fix: fetch advance payment entries on pos invoice

(cherry picked from commit a7078e5702)

Co-authored-by: Diptanil Saha <diptanilsaha@Diptanils-MacBook-Air.local>
2024-12-30 13:59:42 +05:30
mergify[bot]
40f46b76fa fix (pos closing entry): validation for 100 pc discount on pos invoice (backport #44899) (#44930)
fix (pos closing entry): validation for 100 pc discount on pos invoice (#44899)

(cherry picked from commit cfcc24a341)

Co-authored-by: Diptanil Saha <50792171+diptanilsaha@users.noreply.github.com>
2024-12-30 13:58:58 +05:30
mergify[bot]
7dd2b0c189 fix: pos payment using non-default mode of payment (backport #44920) (#44971)
fix: pos payment using non-default mode of payment (#44920)

* fix: pos payment using non-default mode of payment (#41108)

* fix: included css syntax

* refactor: created a function to sanitize the class name

* refactor: reusing method to sanitize class name

* refactor: function rename

(cherry picked from commit 98cbb7e900)

Co-authored-by: Diptanil Saha <diptanil.dev@gmail.com>
2024-12-30 13:56:55 +05:30
ruthra kumar
3a361eac4e Merge pull request #44966 from frappe/mergify/bp/version-15-hotfix/pr-44921
fix: get item tax template based on posting date (backport #44921)
2024-12-30 13:09:15 +05:30
ruthra kumar
61ee292957 Merge pull request #44955 from frappe/mergify/bp/version-15-hotfix/pr-44636
fix: SQL syntax error in Purchase Receipt query for empty filters (backport #44636)
2024-12-30 12:38:02 +05:30
ruthra kumar
c2d9ac11f0 Merge pull request #44960 from frappe/mergify/bp/version-15-hotfix/pr-44958
refactor(test): make manufacturing test idempotent (backport #44958)
2024-12-30 12:37:37 +05:30
ljain112
2feeebb5fb fix: get item tax template based on posting date
(cherry picked from commit 976e35d547)
2024-12-30 12:35:07 +05:30
ruthra kumar
f6a9051291 Merge pull request #44963 from frappe/mergify/bp/version-15-hotfix/pr-44906
fix: set/update billing address on change of company (backport #44906)
2024-12-30 12:27:59 +05:30
Sanket322
e582ff862e fix: use meta to check field instead of doctype
(cherry picked from commit 187c74ae09)
2024-12-30 06:52:56 +00:00
Sanket322
98631eb266 fix: move code from purchase invoice to buying controller
(cherry picked from commit cb197fd01f)
2024-12-30 06:52:55 +00:00
Sanket322
cfa432dbca fix: set/update billing address on change of company
(cherry picked from commit 0adfebee85)
2024-12-30 06:52:55 +00:00
ruthra kumar
a11f7d5a82 chore: fix linter 2024-12-30 12:18:09 +05:30
ruthra kumar
f83112520d refactor(test): make manufacturing test idempotent
(cherry picked from commit f3be246df3)
2024-12-30 12:02:01 +05:30
Navin R C
2f279a6eb4 fix: SQL syntax error in Purchase Receipt query for empty filters (#44636)
fix(po-analysis): handle SQL error due to empty data in IN() clause

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit 48b49cdea4)
2024-12-30 04:11:08 +00:00
Sanket322
6c206c1cb3 fix: ignore duplicate while creating default templates
(cherry picked from commit 9368485594)
2024-12-27 09:17:44 +00:00
ruthra kumar
6bb2b76040 Merge pull request #44935 from frappe/mergify/bp/version-15-hotfix/pr-44892
fix: clear payment schedule in purchase invoice for is_paid (backport #44892)
2024-12-27 13:52:26 +05:30
ruthra kumar
508435ac9f refactor: early return is always better
validate_advance_entries() has a heavy IO bound operation. Early
return on unwanted cases is always better.

(cherry picked from commit 0589fa7f3e)
2024-12-27 07:54:44 +00:00
Sanket322
6cc70605fa fix: clear payment schedule in purchase invoice for is_paid
(cherry picked from commit e1fc239f3d)
2024-12-27 07:54:44 +00:00
mergify[bot]
488d8080c8 fix: strings for translation (backport #44816) (#44927)
* fix: strings for translation

(cherry picked from commit 3be633f6f1)

# Conflicts:
#	erpnext/accounts/utils.py

* fix: resolved conflict

---------

Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2024-12-27 11:33:09 +05:30
mergify[bot]
5cc9e10923 fix: Show order tax amount in customer currency on the portal (backport #44915) (#44923)
fix: Show order tax amount in customer currency on the portal (#44915)

(cherry picked from commit b998933ef0)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2024-12-27 11:24:57 +05:30
ruthra kumar
b45b77df93 Merge pull request #44926 from frappe/mergify/bp/version-15-hotfix/pr-44815
fix: relabel rate to tax rate (backport #44815)
2024-12-27 10:55:54 +05:30
mahsem
991a3366a8 fix: relabel rate to tax rate
(cherry picked from commit 1eb8b0ceef)
2024-12-27 05:25:16 +00:00
mergify[bot]
042d12b2c1 fix: material request status (backport #44917) (#44918)
* fix: material request status (#44917)

(cherry picked from commit 1319ce4bc1)

* chore: fix test case

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-26 22:58:29 +05:30
mergify[bot]
c241262266 fix: not able to make purchase receipt from SCR (backport #44919) (#44925)
* fix: not able to make purchase receipt from SCR (#44919)

(cherry picked from commit ab1cca0c40)

* chore: fix test case

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-26 22:58:11 +05:30
mergify[bot]
9060e4ce57 fix: allow zero valuation rate (backport #44902) (#44910)
fix: allow zero valuation rate (#44902)

(cherry picked from commit 614a8f106d)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-26 11:03:13 +05:30
vishakhdesai
61367ee1ed fix: failing tests fixed
(cherry picked from commit 2ce07865d3)
2024-12-24 12:23:58 +00:00
vishakhdesai
944dc966bc fix: refactor query in get_total_allocated_amount in bank_transaction
(cherry picked from commit 6b847cdb62)
2024-12-24 12:23:58 +00:00
ruthra kumar
9c6832a622 Merge pull request #44894 from frappe/mergify/bp/version-15-hotfix/pr-44878
fix: show profit and loss after period closing (backport #44878)
2024-12-24 17:52:53 +05:30
venkat102
771632a5e2 fix: show profit and loss after period closing
(cherry picked from commit dc5cd93bf0)
2024-12-24 11:33:39 +00:00
ruthra kumar
7a81c0f10f Merge pull request #44891 from frappe/mergify/bp/version-15-hotfix/pr-44889
refactor: use db_set instead of set_value to trigger notification (backport #44889)
2024-12-24 17:02:58 +05:30
venkat102
69ed2a9dfe refactor: use db_set instead of set_value to trigger notification
(cherry picked from commit f8b923edfe)
2024-12-24 11:12:44 +00:00
mergify[bot]
57e6ed4645 fix: set project in GL from the SLE (backport #44879) (#44885)
fix: set project in GL from the SLE (#44879)

(cherry picked from commit 021d077808)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-24 15:12:32 +05:30
ruthra kumar
b48f46ed5a Merge pull request #44882 from frappe/mergify/bp/version-15-hotfix/pr-44794
fix: correct tds rate with lower deduction certificate (backport #44794)
2024-12-24 14:37:35 +05:30
ruthra kumar
72b720477c Merge pull request #44872 from frappe/mergify/bp/version-15-hotfix/pr-44870
fix: posting_time issue (backport #44870)
2024-12-24 13:56:10 +05:30
ljain112
5ca60f3e0a fix: correct tds rate with lower deduction certificate
(cherry picked from commit cb9c12d495)
2024-12-24 08:16:03 +00:00
ruthra kumar
ddc58f0146 Merge pull request #44877 from Abdeali099/backport-uom-handle
fix: add `Stock UOM` when adding new item in POS list (#44780)
2024-12-24 11:18:47 +05:30
Abdeali Chharchhodawala
96cc9e29a1 fix: add Stock UOM when adding new item in POS list (#44780) 2024-12-24 11:08:54 +05:30
mergify[bot]
ca7c229e86 fix: Paid + Write Off Amount issue in Sales Invoice (backport #44763) (#44874)
fix: Paid + Write Off Amount issue in Sales Invoice

(cherry picked from commit 1fd7ba7c88)

Co-authored-by: vishakhdesai <vishakhdesai@gmail.com>
2024-12-24 10:48:15 +05:30
rohitwaghchaure
90aadcdcbc chore: fix conflicts 2024-12-24 10:16:53 +05:30
rohitwaghchaure
7a5c30fe9b fix: posting_time issue (#44870)
(cherry picked from commit 079b86044e)

# Conflicts:
#	erpnext/stock/deprecated_serial_batch.py
2024-12-24 03:37:25 +00:00
Khushi Rawat
f0671d45de Merge pull request #44871 from frappe/mergify/bp/version-15-hotfix/pr-44854
fix: asset fields precision check (backport #44854)
2024-12-24 00:21:26 +05:30
Khushi Rawat
ddfead2cde fix: test case correction
(cherry picked from commit 2f7e6230a6)
2024-12-23 18:31:28 +00:00
Khushi Rawat
516a325a31 fix: asset field precision check
(cherry picked from commit 92b8768ae2)
2024-12-23 18:31:28 +00:00
mergify[bot]
d6001e5ef9 fix: stock entry not fetching expired batches (backport #44863) (#44868)
fix: stock entry not fetching expired batches (#44863)

(cherry picked from commit c9b143b509)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 22:31:16 +05:30
mergify[bot]
f32cf84413 fix: Warehouse wise Stock Value chart roles (backport #44865) (#44867)
fix: Warehouse wise Stock Value chart roles (#44865)

(cherry picked from commit 7d41805d0e)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 22:30:58 +05:30
mergify[bot]
8092d58d9c fix: do not validate qc for scrap item (backport #44844) (#44853)
fix: do not validate qc for scrap item (#44844)

(cherry picked from commit a2c2b8b5ad)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 19:15:46 +05:30
mergify[bot]
3f3df7ef2a perf: slow query related to stock ledger entry (backport #44861) (#44862)
perf: slow query related to stock ledger entry (#44861)

(cherry picked from commit 54d7b742ab)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 19:15:32 +05:30
mergify[bot]
0969877cd7 fix: incoming rate should be zero for rejected items (backport #44857) (#44859)
fix: incoming rate should be zero for rejected items (#44857)

(cherry picked from commit a515a399cf)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 18:11:53 +05:30
ruthra kumar
9543a4c66f Merge pull request #44852 from frappe/mergify/bp/version-15-hotfix/pr-44716
fix: avg. buying amount for product bundle item with serial and batch no in gross profit report (backport #44716)
2024-12-23 15:35:43 +05:30
ruthra kumar
3eb56cbbfb Merge pull request #44841 from frappe/mergify/bp/version-15-hotfix/pr-44808
feat: Added difference_posting_date field in Sales Invoice Advance and Purchase Invoice Advance (backport #44808)
2024-12-23 15:18:16 +05:30
ruthra kumar
dc1ed406a1 Merge pull request #44849 from frappe/mergify/bp/version-15-hotfix/pr-44797
fix(ux): purchase invoice link in error message (backport #44797)
2024-12-23 15:17:47 +05:30
ljain112
2edb6f3224 fix: avg. buying amount for product bundle item with serial and batch no in gross profit report
(cherry picked from commit cc3f4bb0b0)
2024-12-23 09:34:15 +00:00
ruthra kumar
4e23e3191d Merge pull request #44847 from frappe/mergify/bp/version-15-hotfix/pr-44738
fix: fetch tax withholding category from the voucher (backport #44738)
2024-12-23 15:03:02 +05:30
ruthra kumar
55470fefdb Merge pull request #44843 from frappe/mergify/bp/version-15-hotfix/pr-44813
fix: update correct cost center in Accounts Receivable Report (backport #44813)
2024-12-23 14:55:45 +05:30
ruthra kumar
33fc987d95 refactor(test): remove hardcoded names 2024-12-23 14:52:02 +05:30
Nijith anil
01c1ed98ac fix(ux): purchase invoice link in error message (#44797)
* fix(ux): purchase invoice link in error message

* chore: fix linter

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit 6f00a87a9c)
2024-12-23 09:20:17 +00:00
venkat102
8f8dd1c088 fix: fetch tax withholding category from the voucher
(cherry picked from commit 09e64594db)
2024-12-23 09:07:13 +00:00
venkat102
f005bef218 chore: use get function
(cherry picked from commit 1663c7983e)
2024-12-23 09:07:13 +00:00
vishakhdesai
19a8ea217e fix: do not set cost_center update_voucher_balance as it is set in init_voucher_balance
(cherry picked from commit 3b36ce560c)
2024-12-23 08:51:37 +00:00
vishakhdesai
4c5540aef9 fix: update correct cost center in Accounts Receivable Report
(cherry picked from commit 09776e9a5a)
2024-12-23 08:51:36 +00:00
ruthra kumar
cf4068d1a3 chore: resolve conflicts 2024-12-23 14:11:05 +05:30
rs-rethik
3ab4acfafa fix: update query
(cherry picked from commit 854e37c05c)
2024-12-23 08:39:55 +00:00
rs-rethik
13123a0412 refactor: convert sql query to query builder
(cherry picked from commit 2d58e845e6)
2024-12-23 08:39:54 +00:00
rs-rethik
4884849f23 test: add unit test to validate journal entry posting date
(cherry picked from commit c14a2d73bf)
2024-12-23 08:39:54 +00:00
rs-rethik
7498cdf644 feat: use difference_posting_date for journal entry posting_date
(cherry picked from commit ff1d040a6e)
2024-12-23 08:39:54 +00:00
rs-rethik
bec1f972b3 feat: add difference_posting_date field
(cherry picked from commit 225e56cbca)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
#	erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
2024-12-23 08:39:54 +00:00
mergify[bot]
497029f958 fix: incorrect Material Transferred for Manufacturing qty (backport #44823) (#44832)
* fix: incorrect Material Transferred for Manufacturing qty (#44823)

(cherry picked from commit fe0036e707)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-23 13:38:21 +05:30
ruthra kumar
b0a10c6b2a Merge pull request #44840 from frappe/mergify/bp/version-15-hotfix/pr-44786
fix: correct args for get_advance_payment_entries_for_regional (backport #44786)
2024-12-23 13:23:27 +05:30
ljain112
875797e655 fix: correct args for get_advance_payment_entries_for_regional
(cherry picked from commit df13a4cc2f)
2024-12-23 07:28:34 +00:00
ruthra kumar
1a9dfec115 Merge pull request #44838 from frappe/mergify/bp/version-15-hotfix/pr-44751
fix: buying rate for service item in gross profit report (backport #44751)
2024-12-23 12:06:23 +05:30
ruthra kumar
59841408ac Merge pull request #44836 from frappe/mergify/bp/version-15-hotfix/pr-44681
fix: Stock Entry uses incorrect company when generated from Pick List (backport #44679) (backport #44681)
2024-12-23 11:56:32 +05:30
ljain112
a55aaea5a1 fix: buying rate for service item in gross profit report
(cherry picked from commit 8d6e79a16f)
2024-12-23 06:16:24 +00:00
ruthra kumar
041d94f3cf Merge pull request #44834 from frappe/mergify/bp/version-15-hotfix/pr-44644
fix: permissions for marking Quotation as lost (backport #44644)
2024-12-23 11:31:47 +05:30
Nicolas Pereira
58e846709e fix: Stock Entry uses incorrect company when generated from Pick List (#44679)
(cherry picked from commit 00898be8e4)
(cherry picked from commit cd693b5fa4)
2024-12-23 05:59:10 +00:00
ruthra kumar
7f5c19a81e Merge pull request #44830 from frappe/mergify/bp/version-15-hotfix/pr-44826
fix: Remove typo from `Bank Account` on trash (backport #44826)
2024-12-23 11:26:19 +05:30
ruthra kumar
3d4a4e661c chore: resolve conflicts 2024-12-23 11:14:33 +05:30
barredterra
e6390bfba1 fix: permissions for marking Quotation as lost
(cherry picked from commit 4d5241486f)

# Conflicts:
#	erpnext/crm/doctype/competitor/competitor.json
#	erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json
2024-12-23 05:41:57 +00:00
Abdeali Chharchhoda
13a3c816d7 fix: Remove typo
(cherry picked from commit ba28f6bf73)
2024-12-23 04:52:25 +00:00
ruthra kumar
4869847bc7 Merge pull request #44828 from frappe/mergify/bp/version-15-hotfix/pr-44825
fix: Ledger repost support for extending app doctypes (backport #44825)
2024-12-23 10:19:08 +05:30
Deepesh Garg
b5596d98e3 fix: Add hooks for repost allowed doctypes
(cherry picked from commit 919abd2c03)
2024-12-23 04:19:48 +00:00
Deepesh Garg
d137f780bd fix: Ledger repost support for extending app doctypes
(cherry picked from commit ed231abb54)
2024-12-23 04:19:48 +00:00
mergify[bot]
16b013fab2 fix: Duplicate entry ' EF1DE8B2E1B6' for key 'PRIMARY' (backport #44809) (#44814)
fix: Duplicate entry ' EF1DE8B2E1B6' for key 'PRIMARY' (#44809)

(cherry picked from commit 56f561cdaa)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-21 10:26:25 +05:30
mergify[bot]
ad57e33cd7 fix: slow posting datetime update (backport #44799) (#44805)
fix: slow posting datetime update (#44799)

(cherry picked from commit a7b5e2565b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-20 19:38:52 +05:30
Smit Vora
58c0e24f2d Merge pull request #44735 from frappe/mergify/bp/version-15-hotfix/pr-44660
fix: better indicator base amount for Tax Witholding in Journal Entry (backport #44660)
2024-12-20 16:28:43 +05:30
ruthra kumar
162380d9da Merge pull request #44810 from frappe/mergify/bp/version-15-hotfix/pr-44703
fix: Swedish tax templates (backport #44703)
2024-12-20 14:32:41 +05:30
mahsem
cc1834b0cc fix: Swedish tax templates
(cherry picked from commit 73112fa3c9)
2024-12-20 08:42:38 +00:00
ruthra kumar
9449055b1e Merge pull request #44807 from frappe/mergify/bp/version-15-hotfix/pr-44665
fix: add Swedish_2024_COA (backport #44665)
2024-12-20 12:24:44 +05:30
mergify[bot]
8f811728d9 feat(subcontracting): Added provision to create multiple Subcontracting Orders against a single Purchase Order (backport #44711) (#44782)
* feat(subcontracting): Added provision to create multiple Subcontracting Orders against a single Purchase Order (#44711)

* feat(subcontracting): Added provision to create multiple Subcontracting Orders from a single Subcontracted Purchase Order

* refactor(new_sc_flow_2): Fixed error thrown by semgrep

(cherry picked from commit 3eba6bf3dd)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.js
#	erpnext/buying/doctype/purchase_order/test_purchase_order.py
#	erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
#	erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
#	erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json

* feat(subcontracting): Added provision to create multiple Subcontracting Orders against a single Purchase Order (#44711)

* feat(subcontracting): Added provision to create multiple Subcontracting Orders from a single Subcontracted Purchase Order

* refactor(new_sc_flow_2): Fixed error thrown by semgrep

* fix: Resolved errors and removed code from develop branch merged by mistake

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2024-12-20 12:18:01 +05:30
mahsem
98cc79d942 feat: swedish COA
(cherry picked from commit 8a5f7ec4d7)
2024-12-20 06:27:14 +00:00
Smit Vora
db9a319104 fix: typerror on TDS payable monthly report (backport #37707) 2024-12-20 11:02:33 +05:30
mergify[bot]
cdeec8d24c fix: closing stock balance permissions (backport #44791) (#44793)
* fix: closing stock balance permissions (#44791)

(cherry picked from commit 3662a6a41d)

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

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-20 09:35:51 +05:30
mergify[bot]
b706a8274f perf: SABB (backport #44764) (#44789)
perf: SABB (#44764)

(cherry picked from commit 90baa38f64)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-19 13:31:28 +05:30
mergify[bot]
20324224d3 fix: POS Closing entry issue (backport #44772) (#44781)
fix: POS Closing entry issue (#44772)

(cherry picked from commit 779dd2d798)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-12-18 20:38:56 +05:30
ruthra kumar
418ef81dbc Merge pull request #44778 from frappe/mergify/bp/version-15-hotfix/pr-44776
fix: added docs.frappe.io in documentation_url (backport #44776)
2024-12-18 17:53:31 +05:30
Nabin Hait
0fd7792f49 fix: added docs.frappe.io in documentation_url (#44776)
(cherry picked from commit febdf4c61e)
2024-12-18 11:59:50 +00:00
ruthra kumar
7350aa299e Merge pull request #44770 from frappe/mergify/bp/version-15-hotfix/pr-44765
fix: 'str' object has no attribute 'get_sql' (backport #44765)
2024-12-18 14:34:09 +05:30
ljain112
8b810f5fb8 fix: 'str' object has no attribute 'get_sql'
(cherry picked from commit 9a43acb65c)
2024-12-18 08:44:27 +00:00
ruthra kumar
3ed7f761ba Merge pull request #44760 from frappe/mergify/bp/version-15-hotfix/pr-44758
fix: use utility method to generate url (backport #44758)
2024-12-18 11:17:09 +05:30
ruthra kumar
75aee42635 fix: use utility method to generate url
(cherry picked from commit b970eb8b15)
2024-12-18 05:33:31 +00:00
Smit Vora
218e777423 fix: better indicator base amount for Tax Witholding in Journal Entry
(cherry picked from commit 56a0a0db18)
2024-12-17 06:20:31 +00:00
122 changed files with 8366 additions and 555 deletions

View File

@@ -10,6 +10,7 @@ WEBSITE_REPOS = [
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"docs.frappe.io",
"frappeframework.com",
]

View File

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

View File

@@ -128,7 +128,7 @@
"description": "Rate at which this tax is applied",
"fieldname": "tax_rate",
"fieldtype": "Float",
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency"
},

View File

@@ -237,19 +237,22 @@ frappe.treeview_settings["Account"] = {
},
post_render: function (treeview) {
frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
} else {
treeview.new_node();
}
},
"add"
);
if (treeview.can_create) {
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [
root_company,
]);
} else {
treeview.new_node();
}
},
"add"
);
}
},
toolbar: [
{

View File

@@ -101,7 +101,7 @@
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},

View File

@@ -48,7 +48,7 @@ class BankAccount(Document):
self.name = self.account_name + " - " + self.bank
def on_trash(self):
delete_contact_and_address("BankAccount", self.name)
delete_contact_and_address("Bank Account", self.name)
def validate(self):
self.validate_company()

View File

@@ -117,9 +117,9 @@ class BankClearance(Document):
)
else:
frappe.db.set_value(
d.payment_document, d.payment_entry, "clearance_date", d.clearance_date
)
# using db_set to trigger notification
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
payment_entry.db_set("clearance_date", d.clearance_date)
clearance_date_updated = True

View File

@@ -12,6 +12,7 @@ from frappe.utils import cint, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.party import get_party_account
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
get_entries,
@@ -304,54 +305,56 @@ def create_payment_entry_bts(
bank_transaction = frappe.db.get_values(
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
fieldname=["name", "unallocated_amount", "deposit", "bank_account", "currency"],
as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company": company,
"payment_type": payment_type,
"reference_no": reference_number,
"reference_date": reference_date,
"party_type": party_type,
"party": party,
"posting_date": posting_date,
"paid_amount": paid_amount,
"received_amount": paid_amount,
}
payment_entry = frappe.new_doc("Payment Entry")
bank_account = frappe.get_cached_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_cached_value("Account", bank_account, "company")
party_account = get_party_account(party_type, party, company)
payment_entry.update(payment_entry_dict)
bank_currency = bank_transaction.currency
party_currency = frappe.get_cached_value("Account", party_account, "account_currency")
if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment
if project:
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
payment_entry.paid_from = company_account
exc_rate = get_exchange_rate(bank_currency, party_currency, posting_date)
payment_entry.validate()
amt_in_bank_acc_currency = bank_transaction.unallocated_amount
amount_in_party_currency = bank_transaction.unallocated_amount * exc_rate
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = company
pe.reference_no = reference_number
pe.reference_date = reference_date
pe.party_type = party_type
pe.party = party
pe.posting_date = posting_date
pe.paid_from = party_account if payment_type == "Receive" else bank_account
pe.paid_to = party_account if payment_type == "Pay" else bank_account
pe.paid_from_account_currency = party_currency if payment_type == "Receive" else bank_currency
pe.paid_to_account_currency = party_currency if payment_type == "Pay" else bank_currency
pe.paid_amount = amount_in_party_currency if payment_type == "Receive" else amt_in_bank_acc_currency
pe.received_amount = amount_in_party_currency if payment_type == "Pay" else amt_in_bank_acc_currency
pe.mode_of_payment = mode_of_payment
pe.project = project
pe.cost_center = cost_center
pe.validate()
if allow_edit:
return payment_entry
return pe
payment_entry.insert()
pe.insert()
pe.submit()
payment_entry.submit()
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment_entry.name,
"amount": paid_amount,
"payment_name": pe.name,
"amount": amt_in_bank_acc_currency,
}
]
)
@@ -480,8 +483,12 @@ def get_linked_payments(
def subtract_allocations(gl_account, vouchers):
"Look up & subtract any existing Bank Transaction allocations"
copied = []
voucher_docs = [(voucher.get("doctype"), voucher.get("name")) for voucher in vouchers]
voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
for voucher in vouchers:
rows = get_total_allocated_amount(voucher.get("doctype"), voucher.get("name"))
rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or []
filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
if amount := None if not filtered_row else filtered_row[0]["total"]:
@@ -719,7 +726,7 @@ def get_pe_matching_query(
(ref_rank + amount_rank + party_rank + 1).as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"),
pe.name,
pe.paid_amount_after_tax.as_("paid_amount"),
pe.base_paid_amount_after_tax.as_("paid_amount"),
pe.reference_no,
pe.reference_date,
pe.party,

View File

@@ -49,41 +49,39 @@ class AutoMatchbyAccountIBAN:
return result
def match_account_in_party(self) -> tuple | None:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
or_filters = self.get_or_filters()
"""
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee:
1. Get party from a matching (iban/account no) Bank Account
2. If not found, get party from Employee with matching bank account details (iban/account no)
"""
if not (self.bank_party_account_number or self.bank_party_iban):
# Nothing to match
return None
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
# Search for a matching Bank Account that has party set
party_result = frappe.db.get_all(
"Bank Account",
or_filters=self.get_or_filters(),
filters={"party_type": ("is", "set"), "party": ("is", "set")},
fields=["party", "party_type"],
limit_page_length=1,
)
if result := party_result[0] if party_result else None:
return (result["party_type"], result["party"])
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
# If no party is found, search in Employee (since it has bank account details)
employee_result = frappe.db.get_all(
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1
)
if employee_result:
return ("Employee", employee_result[0])
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
if "bank_ac_no" in or_filters:
or_filters["bank_account_no"] = or_filters.pop("bank_ac_no")
if party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
def get_or_filters(self, party: str | None = None) -> dict:
"""Return OR filters for Bank Account and IBAN"""
or_filters = {}
if self.bank_party_account_number:
or_filters["bank_account_no"] = self.bank_party_account_number
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no"
or_filters[bank_ac_field] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban

View File

@@ -154,10 +154,16 @@ class BankTransaction(Document):
"""
remaining_amount = self.unallocated_amount
to_remove = []
payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self, payment_entry
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
or [],
)
if 0.0 == unallocated_amount:
@@ -232,7 +238,7 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_clearance_details(transaction, payment_entry):
def get_clearance_details(transaction, payment_entry, bt_allocations):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
@@ -241,7 +247,6 @@ def get_clearance_details(transaction, payment_entry):
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry)
unallocated_amount = min(
transaction.unallocated_amount,
@@ -294,44 +299,52 @@ def get_related_bank_gl_entries(doctype, docname):
)
def get_total_allocated_amount(doctype, docname):
def get_total_allocated_amount(docs):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
if not docs:
return {}
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account FROM (
SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account
ba.account AS gl_account,
btp.payment_document,
btp.payment_entry
FROM
`tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE
btp.payment_document = %(doctype)s
AND btp.payment_entry = %(docname)s
(btp.payment_document, btp.payment_entry) IN %(docs)s
AND bt.docstatus = 1
WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC)
) temp
WHERE
rownum = 1
""",
dict(doctype=doctype, docname=docname),
dict(docs=docs),
as_dict=True,
)
payment_allocation_details = {}
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
return result
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row)
return payment_allocation_details
def get_paid_amount(payment_entry, currency, gl_bank_account):

View File

@@ -490,13 +490,20 @@ def get_actual_expense(args):
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
for d in frappe.db.sql(
"""select mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""",
fiscal_year,
as_dict=1,
):
mdp = frappe.qb.DocType("Monthly Distribution Percentage")
md = frappe.qb.DocType("Monthly Distribution")
res = (
frappe.qb.from_(mdp)
.join(md)
.on(mdp.parent == md.name)
.select(mdp.month, mdp.percentage_allocation)
.where(md.fiscal_year == fiscal_year)
.where(md.name == monthly_distribution)
.run(as_dict=True)
)
for d in res:
distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.get_cached_value("Fiscal Year", fiscal_year, "year_start_date")

View File

@@ -13,7 +13,11 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
from erpnext.accounts.party import (
validate_account_party_type,
validate_party_frozen_disabled,
validate_party_gle_currency,
)
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency
@@ -268,8 +272,12 @@ class GLEntry(Document):
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)
validate_account_party_type(self)
def validate_currency(self):
if self.is_cancelled:
return
company_currency = erpnext.get_company_currency(self.company)
account_currency = get_account_currency(self.account)

View File

@@ -79,3 +79,48 @@ class TestGLEntry(unittest.TestCase):
"SELECT current from tabSeries where name = %s", naming_series
)[0][0]
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
def test_validate_account_party_type(self):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
100,
"_Test Cost Center - _TC",
save=False,
submit=False,
)
for row in jv.accounts:
row.party_type = "Supplier"
break
jv.save()
try:
jv.submit()
except Exception as e:
self.assertEqual(
str(e),
"Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC",
)
jv1 = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
100,
"_Test Cost Center - _TC",
save=False,
submit=False,
)
for row in jv.accounts:
row.party_type = "Customer"
break
jv1.save()
try:
jv1.submit()
except Exception as e:
self.assertEqual(
str(e),
"Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC",
)

View File

@@ -27,6 +27,18 @@ frappe.ui.form.on("Payment Entry", {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
// project excluded in setup_dimension_filters
frm.set_query("project", function (doc) {
let filters = {
company: doc.company,
};
if (doc.party_type == "Customer") filters.customer = doc.party;
return {
query: "erpnext.controllers.queries.get_project_name",
filters,
};
});
if (frm.is_new()) {
set_default_party_type(frm);
}

View File

@@ -1479,7 +1479,7 @@ class TestPaymentEntry(FrappeTestCase):
parent_account="Current Liabilities - _TC",
account_name="Advances Paid",
company=company,
account_type="Liability",
account_type="Payable",
)
frappe.db.set_value(

View File

@@ -58,7 +58,9 @@
"payment_account",
"payment_channel",
"payment_order",
"amended_from"
"amended_from",
"column_break_iiuv",
"phone_number"
],
"fields": [
{
@@ -376,6 +378,7 @@
"read_only": 1
},
{
"depends_on": "eval: doc.payment_channel==\"Phone\"",
"fetch_from": "payment_gateway_account.payment_channel",
"fieldname": "payment_channel",
"fieldtype": "Select",
@@ -429,13 +432,22 @@
"fieldtype": "Data",
"label": "Party Name",
"read_only": 1
},
{
"fieldname": "column_break_iiuv",
"fieldtype": "Column Break"
},
{
"fieldname": "phone_number",
"fieldtype": "Data",
"label": "Phone Number"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-10-23 12:23:40.117336",
"modified": "2024-12-27 21:29:10.361894",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",

View File

@@ -224,6 +224,7 @@ class PaymentRequest(Document):
sender=self.email_to,
currency=self.currency,
payment_gateway=self.payment_gateway,
phone_number=self.phone_number,
)
controller.validate_transaction_currency(self.currency)
@@ -635,6 +636,7 @@ def make_payment_request(**args):
"party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account,
"party_name": args.get("party_name") or ref_doc.get("customer_name"),
"phone_number": args.get("phone_number") if args.get("phone_number") else None,
}
)

View File

@@ -12,15 +12,15 @@
</thead>
<tbody>
<tr>
<td class="text-left font-bold">{{ _('Grand Total') }}</td>
<td class="text-left font-bold">{{ _("Grand Total") }}</td>
<td class='text-right'> {{ frappe.utils.fmt_money(data.grand_total or '', currency=currency) }}</td>
</tr>
<tr>
<td class="text-left font-bold">{{ _('Net Total') }}</td>
<td class="text-left font-bold">{{ _("Net Total") }}</td>
<td class='text-right'> {{ frappe.utils.fmt_money(data.net_total or '', currency=currency) }}</td>
</tr>
<tr>
<td class="text-left font-bold">{{ _('Total Quantity') }}</td>
<td class="text-left font-bold">{{ _("Total Quantity") }}</td>
<td class='text-right'>{{ data.total_quantity or '' }}</td>
</tr>
@@ -44,7 +44,7 @@
<tbody>
{% for d in data.payment_reconciliation %}
<tr>
<td class="text-left">{{ d.mode_of_payment }}</td>
<td class="text-left">{{ _(d.mode_of_payment) }}</td>
<td class='text-right'> {{ frappe.utils.fmt_money(d.expected_amount - d.opening_amount, currency=currency) }}</td>
</tr>
{% endfor %}
@@ -63,7 +63,7 @@
<thead>
<tr>
<th class="text-left">{{ _("Account") }}</th>
<th class="text-left">{{ _("Rate") }}</th>
<th class="text-left">{{ _("Tax Rate") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
</thead>

View File

@@ -14,7 +14,7 @@
"fieldname": "rate",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"read_only": 1
},
{

View File

@@ -117,11 +117,13 @@ class POSInvoiceMergeLog(Document):
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
sales_invoice, credit_note = "", ""
sales_invoice_doc = None
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
sales_invoice_doc = self.process_merging_into_sales_invoice(sales)
sales_invoice = sales_invoice_doc.name
if returns:
credit_note = self.process_merging_into_credit_note(returns, sales_invoice)
credit_note = self.process_merging_into_credit_note(returns, sales_invoice_doc)
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
@@ -152,15 +154,23 @@ class POSInvoiceMergeLog(Document):
self.consolidated_invoice = sales_invoice.name
return sales_invoice.name
return sales_invoice
def process_merging_into_credit_note(self, data, sales_invoice):
def process_merging_into_credit_note(self, data, sales_invoice_doc=None):
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
credit_note = self.merge_pos_invoice_into(credit_note, data)
referenes = {}
credit_note.return_against = sales_invoice
if sales_invoice_doc:
credit_note.return_against = sales_invoice_doc.name
for d in sales_invoice_doc.items:
referenes[d.item_code] = d.name
for d in credit_note.items:
d.sales_invoice_item = referenes.get(d.item_code)
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
@@ -366,7 +376,12 @@ class POSInvoiceMergeLog(Document):
return []
def cancel_linked_invoices(self):
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
invoices = [self.consolidated_invoice, self.consolidated_credit_note]
if not invoices:
return
invoices.reverse()
for si_name in invoices:
if not si_name:
continue
si = frappe.get_doc("Sales Invoice", si_name)

View File

@@ -399,6 +399,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
hide_fields(this.frm.doc);
if (cint(this.frm.doc.is_paid)) {
this.frm.set_value("allocate_advances_automatically", 0);
this.frm.set_value("payment_terms_template", "");
this.frm.set_value("payment_schedule", []);
if (!this.frm.doc.company) {
this.frm.set_value("is_paid", 0);
frappe.msgprint(__("Please specify Company to proceed"));

View File

@@ -1677,7 +1677,12 @@ class PurchaseInvoice(BuyingController):
if pi:
pi = pi[0][0]
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
frappe.throw(
_("Supplier Invoice No exists in Purchase Invoice {0}").format(
get_link_to_form("Purchase Invoice", pi)
)
)
def update_billing_status_in_pr(self, update_modified=True):
if self.is_return and not self.update_billed_amount_in_purchase_receipt:

View File

@@ -1838,6 +1838,52 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_adjust_incoming_rate_for_rejected_item(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
# Cost of Item is zero in Purchase Receipt
pr = make_purchase_receipt(qty=1, rejected_qty=1, rate=0)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 0)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 150
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "warehouse": pi.items[0].warehouse},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 150)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Purchase Receipt",
"voucher_no": pr.name,
"warehouse": pi.items[0].rejected_warehouse,
},
"stock_value_difference",
)
self.assertFalse(stock_value_difference)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_item_less_defaults(self):
pi = frappe.new_doc("Purchase Invoice")
pi.supplier = "_Test Supplier"
@@ -2448,6 +2494,34 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(len(actual), 3)
self.assertEqual(expected, actual)
def test_invoice_against_returned_pr(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as make_purchase_invoice_from_pr,
)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_return_against_rejected_warehouse,
)
item = make_item("_Test Item For Invoice Against Returned PR", properties={"is_stock_item": 1}).name
original_value = frappe.db.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
pr = make_purchase_receipt(item_code=item, qty=5, rejected_qty=5, rate=100)
pr_return = make_purchase_return_against_rejected_warehouse(pr.name)
pr_return.submit()
pi = make_purchase_invoice_from_pr(pr.name)
pi.save()
self.assertEqual(pi.items[0].qty, 5.0)
frappe.db.set_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", original_value
)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "180px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -40,7 +41,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -111,13 +112,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-06-23 21:13:18.013816",
"modified": "2024-12-20 12:04:46.729972",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",

View File

@@ -16,6 +16,7 @@ class PurchaseInvoiceAdvance(Document):
advance_amount: DF.Currency
allocated_amount: DF.Currency
difference_posting_date: DF.Date | None
exchange_gain_loss: DF.Currency
parent: DF.Data
parentfield: DF.Data

View File

@@ -507,7 +507,7 @@ class SalesInvoice(SellingController):
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
if len(self.payments) == 0 and self.is_pos and flt(self.grand_total) > 0:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
def check_if_consolidated_invoice(self):
@@ -1002,9 +1002,9 @@ class SalesInvoice(SellingController):
def validate_pos(self):
if self.is_return:
invoice_total = self.rounded_total or self.grand_total
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > 1.0 / (
10.0 ** (self.precision("grand_total") + 1.0)
):
if abs(flt(self.paid_amount)) + abs(flt(self.write_off_amount)) - abs(
flt(invoice_total)
) > 1.0 / (10.0 ** (self.precision("grand_total") + 1.0)):
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
def validate_warehouse(self):

View File

@@ -43,6 +43,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.stock.get_item_details import get_item_tax_map
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
@@ -2873,13 +2874,26 @@ class TestSalesInvoice(FrappeTestCase):
item.save()
sales_invoice = create_sales_invoice(item="T Shirt", rate=700, do_not_submit=True)
item_tax_map = get_item_tax_map(
company=sales_invoice.company,
item_tax_template=sales_invoice.items[0].item_tax_template,
)
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
# Apply discount
sales_invoice.apply_discount_on = "Net Total"
sales_invoice.discount_amount = 300
sales_invoice.save()
item_tax_map = get_item_tax_map(
company=sales_invoice.company,
item_tax_template=sales_invoice.items[0].item_tax_template,
)
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_sales_invoice_with_discount_accounting_enabled(self):

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "250px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -41,7 +42,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -112,13 +113,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-06-23 21:12:57.557731",
"modified": "2024-12-20 11:58:28.962370",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",

View File

@@ -16,6 +16,7 @@ class SalesInvoiceAdvance(Document):
advance_amount: DF.Currency
allocated_amount: DF.Currency
difference_posting_date: DF.Date | None
exchange_gain_loss: DF.Currency
parent: DF.Data
parentfield: DF.Data

View File

@@ -247,14 +247,14 @@ def get_tax_row_for_tds(tax_details, tax_amount):
}
def get_lower_deduction_certificate(company, tax_details, pan_no):
def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no):
ldc_name = frappe.db.get_value(
"Lower Deduction Certificate",
{
"pan_no": pan_no,
"tax_withholding_category": tax_details.tax_withholding_category,
"valid_from": (">=", tax_details.from_date),
"valid_upto": ("<=", tax_details.to_date),
"valid_from": ("<=", posting_date),
"valid_upto": (">=", posting_date),
"company": company,
},
"name",
@@ -302,7 +302,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_amount = 0
if party_type == "Supplier":
ldc = get_lower_deduction_certificate(inv.company, tax_details, pan_no)
ldc = get_lower_deduction_certificate(inv.company, posting_date, tax_details, pan_no)
if tax_deducted:
net_total = inv.tax_withholding_net_total
if ldc:
@@ -539,7 +539,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
)
supp_credit_amt = supp_jv_credit_amt
supp_credit_amt += inv.tax_withholding_net_total
supp_credit_amt += inv.get("tax_withholding_net_total", 0)
for type in payment_entry_amounts:
if type.payment_type == "Pay":
@@ -551,9 +551,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
if inv.doctype != "Payment Entry":
tax_withholding_net_total = inv.base_tax_withholding_net_total
tax_withholding_net_total = inv.get("base_tax_withholding_net_total", 0)
else:
tax_withholding_net_total = inv.tax_withholding_net_total
tax_withholding_net_total = inv.get("tax_withholding_net_total", 0)
if (threshold and tax_withholding_net_total >= threshold) or (
cumulative_threshold and (supp_credit_amt + supp_inv_credit_amt) >= cumulative_threshold

View File

@@ -759,6 +759,20 @@ def validate_party_frozen_disabled(party_type, party_name):
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
def validate_account_party_type(self):
if self.is_cancelled:
return
if self.party_type and self.party:
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type and (account_type not in ["Receivable", "Payable"]):
frappe.throw(
_(
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"
).format(self.account)
)
def get_dashboard_info(party_type, party, loyalty_program=None):
current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)

View File

@@ -134,7 +134,6 @@ class ReceivablePayableReport:
paid_in_account_currency=0.0,
credit_note_in_account_currency=0.0,
outstanding_in_account_currency=0.0,
cost_center=ple.cost_center,
)
def init_voucher_balance(self):
@@ -150,6 +149,9 @@ class ReceivablePayableReport:
if key not in self.voucher_balance:
self.voucher_balance[key] = self.build_voucher_dict(ple)
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
self.voucher_balance[key].cost_center = ple.cost_center
self.get_invoices(ple)
if self.filters.get("group_by_party"):
@@ -275,9 +277,6 @@ class ReceivablePayableReport:
row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
if not row.cost_center and ple.cost_center:
row.cost_center = str(ple.cost_center)
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)

View File

@@ -142,7 +142,8 @@ def get_journal_entries(filters):
where jvd.parent = jv.name and jv.docstatus=1
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No'""",
and ifnull(jv.is_opening, 'No') = 'No'
and jv.company = %(company)s """,
filters,
as_dict=1,
)
@@ -163,6 +164,7 @@ def get_payment_entries(filters):
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date <= %(report_date)s
and ifnull(clearance_date, '4000-01-01') > %(report_date)s
and company = %(company)s
""",
filters,
as_dict=1,
@@ -181,6 +183,7 @@ def get_pos_entries(filters):
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
and account.name = sip.account and si.posting_date <= %(report_date)s and
ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s
and si.company = %(company)s
order by
si.posting_date ASC, si.name DESC
""",

View File

@@ -350,7 +350,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
gl_entries_by_account,
accounts_by_name,
accounts,
ignore_closing_entries=False,
ignore_closing_entries=ignore_closing_entries,
root_type=root_type,
)

View File

@@ -637,6 +637,7 @@ class GrossProfitGenerator:
packed_item_row = row.copy()
packed_item_row.warehouse = packed_item.warehouse
packed_item_row.qty = packed_item.total_qty * -1
packed_item_row.serial_and_batch_bundle = packed_item.serial_and_batch_bundle
buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code)
return flt(buying_amount, self.currency_precision)
@@ -728,6 +729,7 @@ class GrossProfitGenerator:
"voucher_no": row.parent,
"allow_zero_valuation": True,
"company": self.filters.company,
"item_code": item_code,
}
)
@@ -748,12 +750,13 @@ class GrossProfitGenerator:
.inner_join(purchase_invoice)
.on(purchase_invoice.name == purchase_invoice_item.parent)
.select(
purchase_invoice.name,
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.posting_date <= self.filters.to_date)
.where(purchase_invoice_item.item_code == item_code)
.where(purchase_invoice.is_return == 0)
.where(purchase_invoice_item.parenttype == "Purchase Invoice")
)
if row.project:
@@ -996,6 +999,7 @@ class GrossProfitGenerator:
"is_return": row.is_return,
"cost_center": row.cost_center,
"invoice": row.parent,
"serial_and_batch_bundle": row.serial_and_batch_bundle,
}
)
@@ -1047,6 +1051,7 @@ class GrossProfitGenerator:
pki.rate,
(pki.rate * pki.qty).as_("base_amount"),
pki.parent_detail_docname,
pki.serial_and_batch_bundle,
)
.where(pki.docstatus == 1)
)

View File

@@ -318,7 +318,7 @@ def get_columns(additional_table_columns, filters):
"width": 100,
},
{
"label": _("Rate"),
"label": _("Tax Rate"),
"fieldname": "rate",
"fieldtype": "Float",
"options": "currency",

View File

@@ -70,10 +70,10 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
rate = tax_rate_map.get(tax_withholding_category)
if net_total_map.get((voucher_type, name)):
if voucher_type == "Journal Entry":
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
if rate:
total_amount = grand_total = base_total = tax_amount / (rate / 100)
base_total = min(tax_amount / (rate / 100), net_total_map.get((voucher_type, name))[0])
total_amount = grand_total = base_total
elif voucher_type == "Purchase Invoice":
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(
(voucher_type, name)
@@ -405,7 +405,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"paid_amount_after_tax",
"base_paid_amount",
],
"Journal Entry": ["total_amount"],
"Journal Entry": ["tax_withholding_category", "total_debit"],
}
entries = frappe.get_all(
@@ -427,7 +427,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
elif doctype == "Payment Entry":
value = [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]
else:
value = [entry.total_amount] * 3
value = [entry.total_debit] * 3
net_total_map[(doctype, entry.name)] = value

View File

@@ -130,7 +130,9 @@ def get_fiscal_years(
else:
return ((fy.name, fy.year_start_date, fy.year_end_date),)
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(
_(label), formatdate(transaction_date)
)
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))

View File

@@ -19,6 +19,7 @@ from frappe.utils import (
)
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.assets.doctype.asset.depreciation import (
get_comma_separated_links,
@@ -308,12 +309,14 @@ class Asset(AccountsController):
)
def validate_precision(self):
float_precision = cint(frappe.db.get_default("float_precision")) or 2
if self.gross_purchase_amount:
self.gross_purchase_amount = flt(self.gross_purchase_amount, float_precision)
self.gross_purchase_amount = flt(
self.gross_purchase_amount, self.precision("gross_purchase_amount")
)
if self.opening_accumulated_depreciation:
self.opening_accumulated_depreciation = flt(
self.opening_accumulated_depreciation, float_precision
self.opening_accumulated_depreciation, self.precision("opening_accumulated_depreciation")
)
def validate_asset_values(self):
@@ -487,11 +490,7 @@ class Asset(AccountsController):
def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"):
row.expected_value_after_useful_life = flt(
row.expected_value_after_useful_life, self.precision("gross_purchase_amount")
)
depr_schedule = get_depr_schedule(self.name, "Draft", row.finance_book)
if not depr_schedule:
continue
@@ -889,6 +888,7 @@ def get_asset_naming_series():
@frappe.whitelist()
def make_sales_invoice(asset, item_code, company, serial_no=None):
asset_doc = frappe.get_doc("Asset", asset)
si = frappe.new_doc("Sales Invoice")
si.company = company
si.currency = frappe.get_cached_value("Company", company, "default_currency")
@@ -905,6 +905,16 @@ def make_sales_invoice(asset, item_code, company, serial_no=None):
"qty": 1,
},
)
accounting_dimensions = get_dimensions(with_cost_center_and_project=True)
for dimension in accounting_dimensions[0]:
si.update(
{
dimension["fieldname"]: asset_doc.get(dimension["fieldname"])
or dimension.get("default_dimension")
}
)
si.set_missing_values()
return si

View File

@@ -889,7 +889,7 @@ class TestDepreciationMethods(AssetSetup):
["2030-12-31", 28630.14, 28630.14],
["2031-12-31", 35684.93, 64315.07],
["2032-12-31", 17842.46, 82157.53],
["2033-06-06", 5342.46, 87499.99],
["2033-06-06", 5342.47, 87500.00],
]
schedules = [

View File

@@ -344,7 +344,7 @@ class AssetDepreciationSchedule(Document):
date_of_disposal,
original_schedule_date=schedule_date,
)
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
if depreciation_amount > 0:
self.add_depr_schedule_row(date_of_disposal, depreciation_amount, n)
@@ -430,6 +430,7 @@ class AssetDepreciationSchedule(Document):
if not depreciation_amount:
continue
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
value_after_depreciation = flt(
value_after_depreciation - flt(depreciation_amount),
asset_doc.precision("gross_purchase_amount"),
@@ -443,6 +444,7 @@ class AssetDepreciationSchedule(Document):
depreciation_amount += flt(value_after_depreciation) - flt(
row.expected_value_after_useful_life
)
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
skip_row = True
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
@@ -517,10 +519,13 @@ class AssetDepreciationSchedule(Document):
i - 1
].accumulated_depreciation_amount
else:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
accumulated_depreciation = flt(
self.opening_accumulated_depreciation,
asset_doc.precision("opening_accumulated_depreciation"),
)
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
value_after_depreciation -= flt(d.depreciation_amount)
value_after_depreciation = flt(value_after_depreciation, d.precision("depreciation_amount"))
# for the last row, if depreciation method = Straight Line
if (
@@ -530,12 +535,11 @@ class AssetDepreciationSchedule(Document):
and not date_of_return
and not row.shift_based
):
depreciation_amount += flt(
d.depreciation_amount += flt(
value_after_depreciation - flt(row.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
d.accumulated_depreciation_amount = flt(
accumulated_depreciation, d.precision("accumulated_depreciation_amount")

View File

@@ -400,11 +400,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
);
}
} else {
cur_frm.add_custom_button(
__("Subcontracting Order"),
this.make_subcontracting_order,
__("Create")
);
if (!doc.items.every((item) => item.qty == item.sco_qty)) {
this.frm.add_custom_button(
__("Subcontracting Order"),
() => {
me.make_subcontracting_order();
},
__("Create")
);
}
}
}
}

View File

@@ -867,27 +867,40 @@ def make_inter_company_sales_order(source_name, target_doc=None):
@frappe.whitelist()
def make_subcontracting_order(source_name, target_doc=None, save=False, submit=False, notify=False):
target_doc = get_mapped_subcontracting_order(source_name, target_doc)
if not is_po_fully_subcontracted(source_name):
target_doc = get_mapped_subcontracting_order(source_name, target_doc)
if (save or submit) and frappe.has_permission(target_doc.doctype, "create"):
target_doc.save()
if (save or submit) and frappe.has_permission(target_doc.doctype, "create"):
target_doc.save()
if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc):
try:
target_doc.submit()
except Exception as e:
target_doc.add_comment("Comment", _("Submit Action Failed") + "<br><br>" + str(e))
if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc):
try:
target_doc.submit()
except Exception as e:
target_doc.add_comment("Comment", _("Submit Action Failed") + "<br><br>" + str(e))
if notify:
frappe.msgprint(
_("Subcontracting Order {0} created.").format(
get_link_to_form(target_doc.doctype, target_doc.name)
),
indicator="green",
alert=True,
)
if notify:
frappe.msgprint(
_("Subcontracting Order {0} created.").format(
get_link_to_form(target_doc.doctype, target_doc.name)
),
indicator="green",
alert=True,
)
return target_doc
return target_doc
else:
frappe.throw(_("This PO has been fully subcontracted."))
def is_po_fully_subcontracted(po_name):
table = frappe.qb.DocType("Purchase Order Item")
query = (
frappe.qb.from_(table)
.select(table.name)
.where((table.parent == po_name) & (table.qty != table.sco_qty))
)
return not query.run(as_dict=True)
def get_mapped_subcontracting_order(source_name, target_doc=None):
@@ -931,7 +944,8 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
"material_request": "material_request",
"material_request_item": "material_request_item",
},
"field_no_map": [],
"field_no_map": ["qty", "fg_item_qty", "amount"],
"condition": lambda item: item.qty != item.sco_qty,
},
},
target_doc,
@@ -939,12 +953,3 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
)
return target_doc
@frappe.whitelist()
def is_subcontracting_order_created(po_name) -> bool:
return (
True
if frappe.db.exists("Subcontracting Order", {"purchase_order": po_name, "docstatus": ["=", 1]})
else False
)

View File

@@ -1004,7 +1004,7 @@ class TestPurchaseOrder(FrappeTestCase):
)
def update_items(po, qty):
trans_items = [po.items[0].as_dict()]
trans_items = [po.items[0].as_dict().update({"docname": po.items[0].name})]
trans_items[0]["qty"] = qty
trans_items[0]["fg_item_qty"] = qty
trans_items = json.dumps(trans_items, default=str)
@@ -1059,6 +1059,73 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.items[0].qty, 30)
self.assertEqual(po.items[0].fg_item_qty, 30)
def test_new_sc_flow(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_subcontracting_order
po = create_po_for_sc_testing()
sco = make_subcontracting_order(po.name)
sco.items[0].qty = 5
sco.items.pop(1)
sco.items[1].qty = 25
sco.save()
sco.submit()
# Test - 1: Quantity of Service Items should change based on change in Quantity of its corresponding Finished Goods Item
self.assertEqual(sco.service_items[0].qty, 5)
# Test - 2: Subcontracted Quantity for the PO Items of each line item should be updated accordingly
po.reload()
self.assertEqual(po.items[0].sco_qty, 5)
self.assertEqual(po.items[1].sco_qty, 0)
self.assertEqual(po.items[2].sco_qty, 12.5)
# Test - 3: Amount for both FG Item and its Service Item should be updated correctly based on change in Quantity
self.assertEqual(sco.items[0].amount, 2000)
self.assertEqual(sco.service_items[0].amount, 500)
# Test - 4: Service Items should be removed if its corresponding Finished Good line item is deleted
self.assertEqual(len(sco.service_items), 2)
# Test - 5: Service Item quantity calculation should be based upon conversion factor calculated from its corresponding PO Item
self.assertEqual(sco.service_items[1].qty, 12.5)
sco = make_subcontracting_order(po.name)
sco.items[0].qty = 6
# Test - 6: Saving document should not be allowed if Quantity exceeds available Subcontracting Quantity of any Purchase Order Item
self.assertRaises(frappe.ValidationError, sco.save)
sco.items[0].qty = 5
sco.items.pop()
sco.items.pop()
sco.save()
sco.submit()
sco = make_subcontracting_order(po.name)
# Test - 7: Since line item 1 is now fully subcontracted, new SCO should by default only have the remaining 2 line items
self.assertEqual(len(sco.items), 2)
sco.items.pop(0)
sco.save()
sco.submit()
# Test - 8: Subcontracted Quantity for each PO Item should be subtracted if SCO gets cancelled
po.reload()
self.assertEqual(po.items[2].sco_qty, 25)
sco.cancel()
po.reload()
self.assertEqual(po.items[2].sco_qty, 12.5)
sco = make_subcontracting_order(po.name)
sco.save()
sco.submit()
# Test - 8: Since this PO is now fully subcontracted, creating a new SCO from it should throw error
self.assertRaises(frappe.ValidationError, make_subcontracting_order, po.name)
@change_settings("Buying Settings", {"auto_create_subcontracting_order": 1})
def test_auto_create_subcontracting_order(self):
from erpnext.controllers.tests.test_subcontracting_controller import (
@@ -1124,6 +1191,53 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.per_billed, 100)
def create_po_for_sc_testing():
from erpnext.controllers.tests.test_subcontracting_controller import (
make_bom_for_subcontracted_items,
make_raw_materials,
make_service_items,
make_subcontracted_items,
)
make_subcontracted_items()
make_raw_materials()
make_service_items()
make_bom_for_subcontracted_items()
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 1",
"qty": 10,
"rate": 100,
"fg_item": "Subcontracted Item SA1",
"fg_item_qty": 10,
},
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 2",
"qty": 20,
"rate": 25,
"fg_item": "Subcontracted Item SA2",
"fg_item_qty": 15,
},
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 3",
"qty": 25,
"rate": 10,
"fg_item": "Subcontracted Item SA3",
"fg_item_qty": 50,
},
]
return create_purchase_order(
rm_items=service_items,
is_subcontracted=1,
supplier_warehouse="_Test Warehouse 1 - _TC",
)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
from erpnext.selling.doctype.customer.test_customer import create_internal_customer

View File

@@ -1,7 +1,7 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-05-24 19:29:06",
"creation": "2024-12-09 12:54:24.652161",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
@@ -26,6 +26,7 @@
"quantity_and_rate",
"qty",
"stock_uom",
"sco_qty",
"col_break2",
"uom",
"conversion_factor",
@@ -909,13 +910,21 @@
{
"fieldname": "column_break_fyqr",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "sco_qty",
"fieldtype": "Float",
"label": "Subcontracted Quantity",
"no_copy": 1,
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 11:23:24.859435",
"modified": "2024-12-10 12:11:18.536089",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -80,6 +80,7 @@ class PurchaseOrderItem(Document):
sales_order_item: DF.Data | None
sales_order_packed_item: DF.Data | None
schedule_date: DF.Date
sco_qty: DF.Float
stock_qty: DF.Float
stock_uom: DF.Link
stock_uom_rate: DF.Currency

View File

@@ -18,11 +18,12 @@ def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters)
update_received_amount(data)
if not data:
return [], [], None, []
update_received_amount(data)
data, chart_data = prepare_data(data, filters)
return columns, data, None, chart_data
@@ -103,6 +104,11 @@ def get_received_amount_data(data):
pr = frappe.qb.DocType("Purchase Receipt")
pr_item = frappe.qb.DocType("Purchase Receipt Item")
po_items = [row.name for row in data]
if not po_items:
return frappe._dict()
query = (
frappe.qb.from_(pr)
.inner_join(pr_item)
@@ -111,12 +117,10 @@ def get_received_amount_data(data):
pr_item.purchase_order_item,
Sum(pr_item.base_amount).as_("received_qty_amount"),
)
.where((pr_item.parent == pr.name) & (pr.docstatus == 1))
.where((pr.docstatus == 1) & (pr_item.purchase_order_item.isin(po_items)))
.groupby(pr_item.purchase_order_item)
)
query = query.where(pr_item.purchase_order_item.isin([row.name for row in data]))
data = query.run()
if not data:

View File

@@ -379,13 +379,14 @@ class AccountsController(TransactionBase):
== 1
)
).run()
frappe.db.sql(
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
)
frappe.db.sql(
"delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
(self.doctype, self.name),
)
gle = frappe.qb.DocType("GL Entry")
frappe.qb.from_(gle).delete().where(
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
).run()
sle = frappe.qb.DocType("Stock Ledger Entry")
frappe.qb.from_(sle).delete().where(
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
def remove_serial_and_batch_bundle(self):
bundles = frappe.get_all(
@@ -463,9 +464,16 @@ class AccountsController(TransactionBase):
)
def validate_invoice_documents_schedule(self):
if self.is_return:
if (
self.is_return
or (self.doctype == "Purchase Invoice" and self.is_paid)
or (self.doctype == "Sales Invoice" and self.is_pos)
or self.get("is_opening") == "Yes"
):
self.payment_terms_template = ""
self.payment_schedule = []
if self.is_return:
return
self.validate_payment_schedule_dates()
@@ -1150,11 +1158,12 @@ class AccountsController(TransactionBase):
def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
frappe.db.sql(
"""delete from `tab{}` where parentfield={} and parent = {}
and allocated_amount = 0""".format(childtype, "%s", "%s"),
(parentfield, self.name),
)
doctype = frappe.qb.DocType(childtype)
frappe.qb.from_(doctype).delete().where(
(doctype.parentfield == parentfield)
& (doctype.parent == self.name)
& (doctype.allocated_amount == 0)
).run()
@frappe.whitelist()
def apply_shipping_rule(self):
@@ -1204,6 +1213,7 @@ class AccountsController(TransactionBase):
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
"difference_posting_date": self.posting_date,
}
if d.get("paid_from"):
advance_row["account"] = d.paid_from
@@ -1214,7 +1224,9 @@ class AccountsController(TransactionBase):
def get_advance_entries(self, include_unallocated=True):
party_account = []
if self.doctype == "Sales Invoice":
default_advance_account = None
if self.doctype in ["Sales Invoice", "POS Invoice"]:
party_type = "Customer"
party = self.customer
amount_field = "credit_in_account_currency"
@@ -1229,10 +1241,14 @@ class AccountsController(TransactionBase):
order_doctype = "Purchase Order"
party_account.append(self.credit_to)
party_account.extend(
get_party_account(party_type, party=party, company=self.company, include_advance=True)
party_accounts = get_party_account(
party_type, party=party, company=self.company, include_advance=True
)
if party_accounts:
party_account.append(party_accounts[0])
default_advance_account = party_accounts[1] if len(party_accounts) == 2 else None
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
journal_entries = get_advance_journal_entries(
@@ -1240,7 +1256,13 @@ class AccountsController(TransactionBase):
)
payment_entries = get_advance_payment_entries_for_regional(
party_type, party, party_account, order_doctype, order_list, include_unallocated
party_type,
party,
party_account,
order_doctype,
order_list,
default_advance_account,
include_unallocated,
)
res = journal_entries + payment_entries
@@ -1497,7 +1519,6 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
je = create_gain_loss_journal(
self.company,
args.get("difference_posting_date") if args else self.posting_date,
@@ -1583,6 +1604,7 @@ class AccountsController(TransactionBase):
"Company", self.company, "exchange_gain_loss_account"
),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
"difference_posting_date": d.get("difference_posting_date"),
}
)
lst.append(args)
@@ -2116,11 +2138,9 @@ class AccountsController(TransactionBase):
for adv in self.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
frappe.db.sql(
f"""delete from `tab{self.doctype} Advance`
where name = %s""",
adv.name,
)
doctype = frappe.qb.DocType(self.doctype + " Advance")
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
consider_for_total_advance = False
if consider_for_total_advance:
@@ -2333,6 +2353,7 @@ class AccountsController(TransactionBase):
return
for d in self.get("payment_schedule"):
d.validate_from_to_dates("discount_date", "due_date")
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
frappe.throw(
_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(

View File

@@ -9,6 +9,7 @@ from frappe.utils import cint, flt, getdate
from frappe.utils.data import nowtime
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.party import get_party_details
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
@@ -744,6 +745,7 @@ class BuyingController(SubcontractingController):
def auto_make_assets(self, asset_items):
items_data = get_asset_item_details(asset_items)
messages = []
accounting_dimensions = get_dimensions(with_cost_center_and_project=True)
for d in self.items:
if d.is_fixed_asset:
@@ -755,11 +757,11 @@ class BuyingController(SubcontractingController):
if item_data.get("asset_naming_series"):
created_assets = []
if item_data.get("is_grouped_asset"):
asset = self.make_asset(d, is_grouped_asset=True)
asset = self.make_asset(d, accounting_dimensions, is_grouped_asset=True)
created_assets.append(asset)
else:
for _qty in range(cint(d.qty)):
asset = self.make_asset(d)
asset = self.make_asset(d, accounting_dimensions)
created_assets.append(asset)
if len(created_assets) > 5:
@@ -797,7 +799,7 @@ class BuyingController(SubcontractingController):
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
def make_asset(self, row, is_grouped_asset=False):
def make_asset(self, row, accounting_dimensions, is_grouped_asset=False):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
@@ -828,6 +830,13 @@ class BuyingController(SubcontractingController):
"purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None,
}
)
for dimension in accounting_dimensions[0]:
asset.update(
{
dimension["fieldname"]: self.get(dimension["fieldname"])
or dimension.get("default_dimension")
}
)
asset.flags.ignore_validate = True
asset.flags.ignore_mandatory = True

View File

@@ -271,10 +271,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
qb_filter_or_conditions = []
ifelse = CustomFunction("IF", ["condition", "then", "else"])
if filters and filters.get("customer"):
qb_filter_and_conditions.append(
(proj.customer == filters.get("customer")) | proj.customer.isnull() | proj.customer == ""
)
if filters:
if filters.get("customer"):
qb_filter_and_conditions.append(
(proj.customer == filters.get("customer")) | proj.customer.isnull() | proj.customer == ""
)
if filters.get("company"):
qb_filter_and_conditions.append(proj.company == filters.get("company"))
qb_filter_and_conditions.append(proj.status.notin(["Completed", "Cancelled"]))

View File

@@ -126,9 +126,13 @@ status_map = {
"Partially Received",
"eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
],
[
"Partially Received",
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'",
],
[
"Partially Ordered",
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1",
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type != 'Material Transfer'",
],
[
"Manufactured",

View File

@@ -156,7 +156,7 @@ class StockController(AccountsController):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
is_material_issue = False
if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
if self.doctype == "Stock Entry" and self.purpose in ["Material Issue", "Material Transfer"]:
is_material_issue = True
for d in self.get("items"):
@@ -530,7 +530,7 @@ class StockController(AccountsController):
"account": warehouse_account[sle.warehouse]["account"],
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"project": sle.get("project") or item_row.project or self.get("project"),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening")
@@ -550,7 +550,9 @@ class StockController(AccountsController):
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"project": sle.get("project")
or item_row.get("project")
or self.get("project"),
"is_opening": item_row.get("is_opening")
or self.get("is_opening")
or "No",
@@ -678,23 +680,34 @@ class StockController(AccountsController):
def get_stock_ledger_details(self):
stock_ledger = {}
stock_ledger_entries = frappe.db.sql(
"""
select
name, warehouse, stock_value_difference, valuation_rate,
voucher_detail_no, item_code, posting_date, posting_time,
actual_qty, qty_after_transaction
from
`tabStock Ledger Entry`
where
voucher_type=%s and voucher_no=%s and is_cancelled = 0
""",
(self.doctype, self.name),
as_dict=True,
)
table = frappe.qb.DocType("Stock Ledger Entry")
stock_ledger_entries = (
frappe.qb.from_(table)
.select(
table.name,
table.warehouse,
table.stock_value_difference,
table.valuation_rate,
table.voucher_detail_no,
table.item_code,
table.posting_date,
table.posting_time,
table.actual_qty,
table.qty_after_transaction,
table.project,
)
.where(
(table.voucher_type == self.doctype)
& (table.voucher_no == self.name)
& (table.is_cancelled == 0)
)
).run(as_dict=True)
for sle in stock_ledger_entries:
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger
def check_expense_account(self, item):
@@ -999,6 +1012,9 @@ class StockController(AccountsController):
elif self.doctype == "Stock Entry" and row.t_warehouse:
qi_required = True # inward stock needs inspection
if row.get("is_scrap_item"):
continue
if qi_required: # validate row only if inspection is required on item level
self.validate_qi_presence(row)
if self.docstatus == 1:
@@ -1758,6 +1774,9 @@ def make_bundle_for_material_transfer(**kwargs):
bundle_doc.calculate_qty_and_amount()
bundle_doc.flags.ignore_permissions = True
bundle_doc.flags.ignore_validate = True
bundle_doc.save(ignore_permissions=True)
if kwargs.do_not_submit:
bundle_doc.save(ignore_permissions=True)
else:
bundle_doc.submit()
return bundle_doc.name

View File

@@ -103,6 +103,29 @@ class SubcontractingController(StockController):
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
)
if (
self.doctype == "Subcontracting Order" and not item.sc_conversion_factor
): # this condition will only be true if user has recently updated from develop branch
service_item_qty = frappe.get_value(
"Subcontracting Order Service Item",
filters={"purchase_order_item": item.purchase_order_item, "parent": self.name},
fieldname=["qty"],
)
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
):
frappe.throw(
_(
"Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
).format(item.idx, item.item_name)
)
item.amount = item.qty * item.rate
if item.bom:
is_active, bom_item = frappe.get_value("BOM", item.bom, ["is_active", "item"])
@@ -1110,6 +1133,12 @@ def get_item_details(items):
return item_details
def get_pending_sco_qty(po_name):
table = frappe.qb.DocType("Purchase Order Item")
query = frappe.qb.from_(table).select(table.name, table.qty, table.sco_qty).where(table.parent == po_name)
return {item.name: item.qty - item.sco_qty for item in query.run(as_dict=True)}
@frappe.whitelist()
def make_rm_stock_entry(
subcontract_order, rm_items=None, order_doctype="Subcontracting Order", target_doc=None

View File

@@ -18,7 +18,7 @@ from erpnext.controllers.accounts_controller import (
validate_inclusive_tax,
validate_taxes_and_charges,
)
from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.stock.get_item_details import _get_item_tax_template, get_item_tax_map
from erpnext.utilities.regional import temporary_flag
@@ -70,6 +70,7 @@ class calculate_taxes_and_totals:
self.validate_conversion_rate()
self.calculate_item_values()
self.validate_item_tax_template()
self.update_item_tax_map()
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_net_total()
@@ -134,6 +135,14 @@ class calculate_taxes_and_totals:
)
)
def update_item_tax_map(self):
for item in self.doc.items:
item.item_tax_rate = get_item_tax_map(
company=self.doc.get("company"),
item_tax_template=item.item_tax_template,
as_json=True,
)
def validate_conversion_rate(self):
# validate conversion rate
company_currency = erpnext.get_company_currency(self.doc.company)

View File

@@ -2,6 +2,8 @@
# For license information, please see license.txt
from datetime import datetime
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
@@ -1979,3 +1981,95 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(len(exc_je_for_adv), 0)
self.remove_advance_accounts_from_party_master()
def test_difference_posting_date_in_pi_and_si(self):
self.setup_advance_accounts_in_party_master()
# create payment entry for customer
adv = self.create_payment_entry(amount=1, source_exc_rate=83)
adv.save()
self.assertEqual(adv.paid_from, self.advance_received_usd)
adv.submit()
adv.reload()
# create sales invoice with advance received
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
si.debit_to = self.debtors_usd
si.append(
"advances",
{
"reference_type": adv.doctype,
"reference_name": adv.name,
"remarks": "Amount INR 1 received from _Test MC Customer USD\nTransaction reference no Test001 dated 2024-12-19",
"advance_amount": 1.0,
"allocated_amount": 1.0,
"exchange_gain_loss": 3.0,
"ref_exchange_rate": 83.0,
"difference_posting_date": add_days(nowdate(), -2),
},
)
si.save().submit()
# exc Gain/Loss journal should've been creatad
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(len(exc_je_for_adv), 1)
self.assertEqual(exc_je_for_si, exc_je_for_adv)
# check jv created with difference_posting_date in sales invoice
jv = frappe.get_doc("Journal Entry", exc_je_for_si[0].parent)
sales_invoice = frappe.get_doc("Sales Invoice", si.name)
self.assertEqual(sales_invoice.advances[0].difference_posting_date, jv.posting_date)
# create payment entry for supplier
usd_amount = 1
inr_amount = 85
exc_rate = 85
adv = create_payment_entry(
company=self.company,
payment_type="Pay",
party_type="Supplier",
party=self.supplier,
paid_from=self.cash,
paid_to=self.advance_paid_usd,
paid_amount=inr_amount,
)
adv.source_exchange_rate = 1
adv.target_exchange_rate = exc_rate
adv.received_amount = usd_amount
adv.paid_amount = exc_rate * usd_amount
adv.posting_date = nowdate()
adv.save()
self.assertEqual(adv.paid_to, self.advance_paid_usd)
adv.submit()
# create purchase invoice with advance paid
pi = self.create_purchase_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
pi.append(
"advances",
{
"reference_type": adv.doctype,
"reference_name": adv.name,
"remarks": "Amount INR 1 paid to _Test MC Supplier USD\nTransaction reference no Test001 dated 2024-12-20",
"advance_amount": 1.0,
"allocated_amount": 1.0,
"exchange_gain_loss": 5.0,
"ref_exchange_rate": 85.0,
"difference_posting_date": add_days(nowdate(), -2),
},
)
pi.save().submit()
self.assertEqual(pi.credit_to, self.creditors_usd)
# exc Gain/Loss journal should've been creatad
exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
self.assertEqual(len(exc_je_for_pi), 1)
self.assertEqual(len(exc_je_for_adv), 1)
self.assertEqual(exc_je_for_pi, exc_je_for_adv)
# check jv created with difference_posting_date in purchase invoice
journal_voucher = frappe.get_doc("Journal Entry", exc_je_for_pi[0].parent)
purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name)
self.assertEqual(purchase_invoice.advances[0].difference_posting_date, journal_voucher.posting_date)

View File

@@ -1261,6 +1261,7 @@ def make_raw_materials():
for item, properties in raw_materials.items():
if not frappe.db.exists("Item", item):
properties.update({"is_stock_item": 1})
properties.update({"valuation_rate": 100})
make_item(item, properties)
@@ -1311,7 +1312,7 @@ def make_bom_for_subcontracted_items():
for item_code, raw_materials in boms.items():
if not frappe.db.exists("BOM", {"item": item_code}):
make_bom(item=item_code, raw_materials=raw_materials, rate=100)
make_bom(item=item_code, raw_materials=raw_materials, rate=100, currency="INR")
def set_backflush_based_on(based_on):

View File

@@ -38,7 +38,7 @@
"table_fieldname": "competitors"
}
],
"modified": "2023-11-23 19:33:54.284279",
"modified": "2024-12-10 08:26:38.496003",
"modified_by": "Administrator",
"module": "CRM",
"name": "Competitor",
@@ -53,20 +53,25 @@
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"role": "Sales Master Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"share": 1,
"write": 1
"role": "Sales User"
},
{
"read": 1,
"role": "Sales Manager"
},
{
"read": 1,
"role": "Maintenance Manager"
},
{
"read": 1,
"role": "Maintenance User"
}
],
"quick_entry": 1,

View File

@@ -25,7 +25,12 @@ frappe.ui.form.on("Plaid Settings", {
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
freeze: true,
callback: () => {
let bank_transaction_link = '<a href="#List/Bank Transaction">Bank Transaction</a>';
let bank_transaction_link = frappe.utils.get_form_link(
"Bank Transaction",
"",
true,
"Bank Transaction"
);
frappe.msgprint({
title: __("Sync Started"),

View File

@@ -13,6 +13,8 @@
"supplier",
"supplier_name",
"column_break_8",
"order_no",
"order_date",
"from_date",
"to_date",
"company",
@@ -129,15 +131,27 @@
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions Details"
},
{
"fieldname": "order_no",
"fieldtype": "Data",
"label": "Order No"
},
{
"depends_on": "eval:doc.order_no",
"fieldname": "order_date",
"fieldtype": "Date",
"label": "Order Date"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-06-29 00:30:30.621636",
"modified": "2024-12-05 15:44:21.520093",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Blanket Order",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@@ -31,6 +31,8 @@ class BlanketOrder(Document):
from_date: DF.Date
items: DF.Table[BlanketOrderItem]
naming_series: DF.Literal["MFG-BLR-.YYYY.-"]
order_date: DF.Date | None
order_no: DF.Data | None
supplier: DF.Link | None
supplier_name: DF.Data | None
tc_name: DF.Link | None

View File

@@ -3,7 +3,7 @@
<div class="col-md-5" style="max-height: 500px">
{% if data.image %}
<div class="border image-field " style="overflow: hidden;border-color:#e6e6e6">
<img class="responsive" src={{ data.image }}>
<img class="responsive" style="width: 100%;" src={{ data.image }}>
</div>
{% endif %}
</div>

View File

@@ -2,13 +2,13 @@ frappe.listview_settings["BOM"] = {
add_fields: ["is_active", "is_default", "total_cost", "has_variants"],
get_indicator: function (doc) {
if (doc.is_active && doc.has_variants) {
return [__("Template"), "orange", "has_variants,=,Yes"];
return [__("Template"), "orange", "has_variants,=,1"];
} else if (doc.is_default) {
return [__("Default"), "green", "is_default,=,Yes"];
return [__("Default"), "green", "is_default,=,1"];
} else if (doc.is_active) {
return [__("Active"), "blue", "is_active,=,Yes"];
return [__("Active"), "blue", "is_active,=,1"];
} else if (!doc.is_active) {
return [__("Not active"), "gray", "is_active,=,No"];
return [__("Not active"), "gray", "is_active,=,0"];
}
},
};

View File

@@ -9,6 +9,7 @@ if TYPE_CHECKING:
import frappe
from frappe.model.document import Document
from frappe.utils import date_diff, get_datetime, now
class BOMUpdateTool(Document):
@@ -50,13 +51,21 @@ def auto_update_latest_price_in_all_boms() -> None:
if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
wip_log = frappe.get_all(
"BOM Update Log",
{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
fields=["creation", "status"],
filters={"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
limit_page_length=1,
order_by="creation desc",
)
if not wip_log:
if not wip_log or is_older_log(wip_log[0]):
create_bom_update_log(update_type="Update Cost")
def is_older_log(log: dict) -> bool:
no_of_days = date_diff(get_datetime(now()), get_datetime(log.creation))
return no_of_days > 10
def create_bom_update_log(
boms: dict[str, str] | None = None,
update_type: Literal["Replace BOM", "Update Cost"] = "Replace BOM",

View File

@@ -944,8 +944,9 @@ class JobCard(Document):
if doc.transfer_material_against == "Job Card" and not doc.skip_transfer:
min_qty = []
for d in doc.operations:
if d.completed_qty:
min_qty.append(d.completed_qty)
completed_qty = flt(d.completed_qty) + flt(d.process_loss_qty)
if completed_qty:
min_qty.append(completed_qty)
else:
min_qty = []
break

View File

@@ -242,14 +242,14 @@
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
"fieldname": "validate_components_quantities_per_bom",
"fieldtype": "Check",
"label": "Validate Components Quantities Per BOM"
"label": "Validate Components and Quantities Per BOM"
}
],
"icon": "icon-wrench",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-09-02 12:12:03.132567",
"modified": "2025-01-02 12:46:33.520853",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
@@ -267,4 +267,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -7,6 +7,7 @@ from frappe.tests.utils import FrappeTestCase, change_settings, timeout
from frappe.utils import add_days, add_months, add_to_date, cint, flt, now, today
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
from erpnext.manufacturing.doctype.job_card.job_card import make_stock_entry as make_stock_entry_from_jc
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.work_order.work_order import (
CapacityError,
@@ -505,6 +506,60 @@ class TestWorkOrder(FrappeTestCase):
for stock_entry in stock_entries:
stock_entry.cancel()
def test_work_order_material_transferred_qty_with_process_loss(self):
stock_entries = []
bom = frappe.get_doc("BOM", {"docstatus": 1, "with_operations": 1, "company": "_Test Company"})
work_order = make_wo_order_test_record(
item=bom.item,
qty=2,
bom_no=bom.name,
source_warehouse="_Test Warehouse - _TC",
transfer_material_against="Job Card",
)
self.assertEqual(work_order.qty, 2)
for row in work_order.required_items:
stock_entry_doc = test_stock_entry.make_stock_entry(
item_code=row.item_code, target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100
)
stock_entries.append(stock_entry_doc)
job_cards = frappe.get_all(
"Job Card", filters={"work_order": work_order.name}, order_by="creation asc"
)
for row in job_cards:
transfer_entry_1 = make_stock_entry_from_jc(row.name)
transfer_entry_1.submit()
doc = frappe.get_doc("Job Card", row.name)
for row in doc.scheduled_time_logs:
doc.append(
"time_logs",
{
"from_time": row.from_time,
"to_time": row.to_time,
"time_in_mins": row.time_in_mins,
"completed_qty": 1,
},
)
doc.save()
doc.submit()
self.assertEqual(doc.total_completed_qty, 1)
self.assertEqual(doc.process_loss_qty, 1)
work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 2)
for row in work_order.operations:
self.assertEqual(row.completed_qty, 1)
self.assertEqual(row.process_loss_qty, 1)
def test_capcity_planning(self):
frappe.db.set_single_value(
"Manufacturing Settings", {"disable_capacity_planning": 0, "capacity_planning_for_days": 1}
@@ -2346,6 +2401,56 @@ class TestWorkOrder(FrappeTestCase):
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
def test_components_as_per_bom_for_manufacture_entry(self):
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)
fg_item = "Test FG Item For Component Validation 1"
source_warehouse = "Stores - _TC"
raw_materials = ["Test Component Validation RM Item 11", "Test Component Validation RM Item 12"]
make_item(fg_item, {"is_stock_item": 1})
for item in raw_materials:
make_item(item, {"is_stock_item": 1})
test_stock_entry.make_stock_entry(
item_code=item,
target=source_warehouse,
qty=10,
basic_rate=100,
)
make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)
wo = make_wo_order_test_record(
item=fg_item,
qty=10,
source_warehouse=source_warehouse,
)
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
transfer_entry.save()
transfer_entry.remove(transfer_entry.items[0])
self.assertRaises(frappe.ValidationError, transfer_entry.save)
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
transfer_entry.save()
transfer_entry.submit()
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
manufacture_entry.save()
manufacture_entry.remove(manufacture_entry.items[0])
self.assertRaises(frappe.ValidationError, manufacture_entry.save)
manufacture_entry.delete()
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
manufacture_entry.save()
manufacture_entry.submit()
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
def make_operation(**kwargs):
kwargs = frappe._dict(kwargs)

View File

@@ -130,6 +130,32 @@ frappe.ui.form.on("Work Order", {
);
},
allow_alternative_item: function (frm) {
let has_alternative = false;
if (frm.doc.required_items) {
has_alternative = frm.doc.required_items.find((i) => i.allow_alternative_item === 1);
}
if (frm.doc.allow_alternative_item && frm.doc.docstatus === 0 && has_alternative) {
frm.add_custom_button(__("Alternate Item"), () => {
erpnext.utils.select_alternate_items({
frm: frm,
child_docname: "required_items",
warehouse_field: "source_warehouse",
child_doctype: "Work Order Item",
original_item_field: "original_item",
condition: (d) => {
if (d.allow_alternative_item) {
return true;
}
},
});
});
} else {
frm.remove_custom_button(__("Alternate Item"));
}
},
refresh: function (frm) {
erpnext.toggle_naming_series();
erpnext.work_order.set_custom_buttons(frm);
@@ -163,26 +189,6 @@ frappe.ui.form.on("Work Order", {
}
}
if (frm.doc.required_items && frm.doc.allow_alternative_item) {
const has_alternative = frm.doc.required_items.find((i) => i.allow_alternative_item === 1);
if (frm.doc.docstatus == 0 && has_alternative) {
frm.add_custom_button(__("Alternate Item"), () => {
erpnext.utils.select_alternate_items({
frm: frm,
child_docname: "required_items",
warehouse_field: "source_warehouse",
child_doctype: "Work Order Item",
original_item_field: "original_item",
condition: (d) => {
if (d.allow_alternative_item) {
return true;
}
},
});
});
}
}
if (frm.doc.status == "Completed") {
if (frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture") {
frm.add_custom_button(
@@ -210,6 +216,7 @@ frappe.ui.form.on("Work Order", {
}
frm.trigger("add_custom_button_to_return_components");
frm.trigger("allow_alternative_item");
},
add_custom_button_to_return_components: function (frm) {
@@ -540,6 +547,9 @@ frappe.ui.form.on("Work Order", {
});
frappe.ui.form.on("Work Order Item", {
allow_alternative_item(frm) {
frm.trigger("allow_alternative_item");
},
source_warehouse: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (!row.item_code) {
@@ -618,7 +628,7 @@ erpnext.work_order = {
set_custom_buttons: function (frm) {
var doc = frm.doc;
if (doc.status !== "Closed") {
if (doc.docstatus === 1 && doc.status !== "Closed") {
frm.add_custom_button(
__("Close"),
function () {

View File

@@ -230,8 +230,8 @@ class ForecastingReport(ExponentialSmoothingForecast):
"data": {
"labels": labels,
"datasets": [
{"name": "Demand", "values": self.total_demand},
{"name": "Forecast", "values": self.total_forecast},
{"name": _("Demand"), "values": self.total_demand},
{"name": _("Forecast"), "values": self.total_forecast},
],
},
"type": "line",

View File

@@ -106,7 +106,7 @@ def get_data(filters, columns):
for label in labels:
work = {}
work["Status"] = label
work["Status"] = _(label)
for _dummy, end_date in ranges:
period = get_period(end_date, filters)
if periodic_data.get(label).get(period):

View File

@@ -4,61 +4,67 @@ import frappe
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
DEFAULT_FILTERS = {
"company": "_Test Company",
"from_date": "2010-01-01",
"to_date": "2030-01-01",
"warehouse": "_Test Warehouse - _TC",
}
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}),
("BOM Operations Time", {}),
("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}),
("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}),
("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
("Downtime Analysis", {}),
(
"Exponential Smoothing Forecasting",
{
"based_on_document": "Sales Order",
"based_on_field": "Qty",
"no_of_years": 3,
"periodicity": "Yearly",
"smoothing_constant": 0.3,
},
),
("Job Card Summary", {"fiscal_year": "2021-2022"}),
("Production Analytics", {"range": "Monthly"}),
("Quality Inspection Summary", {}),
("Process Loss Report", {}),
("Work Order Stock Report", {}),
("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}),
]
if frappe.db.a_row_exists("Production Plan"):
REPORT_FILTER_TEST_CASES.append(
("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name})
)
OPTIONAL_FILTERS = {
"warehouse": "_Test Warehouse - _TC",
"item": "_Test Item",
"item_group": "_Test Item Group",
}
class TestManufacturingReports(unittest.TestCase):
def setUp(self):
self.setup_default_filters()
def tearDown(self):
frappe.db.rollback()
def setup_default_filters(self):
self.last_bom = frappe.get_last_doc("BOM").name
self.DEFAULT_FILTERS = {
"company": "_Test Company",
"from_date": "2010-01-01",
"to_date": "2030-01-01",
"warehouse": "_Test Warehouse - _TC",
}
self.REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
("BOM Explorer", {"bom": self.last_bom}),
("BOM Operations Time", {}),
("BOM Stock Calculated", {"bom": self.last_bom, "qty_to_make": 2}),
("BOM Stock Report", {"bom": self.last_bom, "qty_to_produce": 2}),
("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
("Downtime Analysis", {}),
(
"Exponential Smoothing Forecasting",
{
"based_on_document": "Sales Order",
"based_on_field": "Qty",
"no_of_years": 3,
"periodicity": "Yearly",
"smoothing_constant": 0.3,
},
),
("Job Card Summary", {"fiscal_year": "2021-2022"}),
("Production Analytics", {"range": "Monthly"}),
("Quality Inspection Summary", {}),
("Process Loss Report", {}),
("Work Order Stock Report", {}),
("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}),
]
if frappe.db.a_row_exists("Production Plan"):
self.REPORT_FILTER_TEST_CASES.append(
("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name})
)
self.OPTIONAL_FILTERS = {
"warehouse": "_Test Warehouse - _TC",
"item": "_Test Item",
"item_group": "_Test Item Group",
}
def test_execute_all_manufacturing_reports(self):
"""Test that all script report in manufacturing modules are executable with supported filters"""
for report, filter in REPORT_FILTER_TEST_CASES:
for report, filter in self.REPORT_FILTER_TEST_CASES:
with self.subTest(report=report):
execute_script_report(
report_name=report,
module="Manufacturing",
filters=filter,
default_filters=DEFAULT_FILTERS,
optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
default_filters=self.DEFAULT_FILTERS,
optional_filters=self.OPTIONAL_FILTERS if filter.get("_optional") else None,
)

View File

@@ -147,10 +147,10 @@ class BOMConfigurator {
if (!node.expanded) {
view.tree.load_children(node, true);
$(node.parent[0]).find(".tree-children").show();
node.$toolbar.find(".expand-all-btn").html("Collapse All");
node.$toolbar.find(".expand-all-btn").html(__("Collapse All"));
} else {
node.$tree_link.trigger("click");
node.$toolbar.find(".expand-all-btn").html("Expand All");
node.$toolbar.find(".expand-all-btn").html(__("Expand All"));
}
},
condition: function (node) {
@@ -190,10 +190,10 @@ class BOMConfigurator {
if (!node.expanded) {
view.tree.load_children(node, true);
$(node.parent[0]).find(".tree-children").show();
node.$toolbar.find(".expand-all-btn").html("Collapse All");
node.$toolbar.find(".expand-all-btn").html(__("Collapse All"));
} else {
node.$tree_link.trigger("click");
node.$toolbar.find(".expand-all-btn").html("Expand All");
node.$toolbar.find(".expand-all-btn").html(__("Expand All"));
}
},
condition: function (node) {

View File

@@ -25,6 +25,14 @@ erpnext.buying = {
};
});
this.frm.set_query("project", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
if (this.frm.doc.__islocal
&& frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) {
@@ -145,6 +153,18 @@ erpnext.buying = {
});
}
company(){
if(!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return;
frappe.call({
method: "erpnext.setup.doctype.company.company.get_default_company_address",
args: { name: this.frm.doc.company, existing_address:this.frm.doc.billing_address },
callback: (r) => {
this.frm.set_value("billing_address", r.message || "");
},
});
}
supplier_address() {
erpnext.utils.get_address_display(this.frm);
erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address");

View File

@@ -959,7 +959,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
let is_drop_ship = me.frm.doc.items.some(item => item.delivered_by_supplier);
if (!is_drop_ship) {
console.log('get_shipping_address');
erpnext.utils.get_shipping_address(this.frm, function() {
set_party_account(set_pricing);
});
@@ -975,6 +974,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
transaction_date() {
this.apply_pricing_rule()
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
@@ -983,6 +983,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
posting_date() {
var me = this;
me.apply_pricing_rule()
if (this.frm.doc.posting_date) {
this.frm.posting_date = this.frm.doc.posting_date;
@@ -2310,6 +2311,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
fieldname: "batch_no",
label: __("Batch No"),
hidden: true
},
{
fieldtype: "Data",
fieldname: "child_row_reference",
label: __("Child Row Reference"),
hidden: true
}
]
}
@@ -2353,14 +2360,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (this.has_inspection_required(item)) {
let dialog_items = dialog.fields_dict.items;
dialog_items.df.data.push({
"docname": item.name,
"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
"sample_size": item.sample_quantity,
"child_row_reference": item.name,
});
dialog_items.grid.refresh();
}

View File

@@ -58,13 +58,17 @@ def search_by_term(search_term, warehouse, price_list):
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
item.update({"actual_qty": item_stock_qty})
price_filters = {
"price_list": price_list,
"item_code": item_code,
}
if batch_no:
price_filters["batch_no"] = batch_no
price = frappe.get_list(
doctype="Item Price",
filters={
"price_list": price_list,
"item_code": item_code,
"batch_no": batch_no,
},
filters=price_filters,
fields=["uom", "currency", "price_list_rate", "batch_no"],
)

View File

@@ -285,6 +285,7 @@ erpnext.PointOfSale.Controller = class {
edit_cart: () => this.payment.edit_cart(),
customer_details_updated: (details) => {
this.item_selector.load_items_data();
this.customer_details = details;
// will add/remove LP payment method
this.payment.render_loyalty_points_payment_mode();
@@ -574,11 +575,19 @@ erpnext.PointOfSale.Controller = class {
} else {
if (!this.frm.doc.customer) return this.raise_customer_selection_alert();
const { item_code, batch_no, serial_no, rate, uom } = item;
const { item_code, batch_no, serial_no, rate, uom, stock_uom } = item;
if (!item_code) return;
const new_item = { item_code, batch_no, rate, uom, [field]: value };
if (rate == undefined || rate == 0) {
frappe.show_alert({
message: __("Price is not set for the item."),
indicator: "orange",
});
frappe.utils.play_sound("error");
return;
}
const new_item = { item_code, batch_no, rate, uom, [field]: value, stock_uom };
if (serial_no) {
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);

View File

@@ -390,6 +390,14 @@ erpnext.PointOfSale.ItemCart = class {
input_class: "input-xs",
onchange: function () {
this.value = flt(this.value);
if (this.value > 100) {
frappe.msgprint({
title: __("Invalid Discount"),
indicator: "red",
message: __("Discount cannot be greater than 100%."),
});
this.value = 0;
}
frappe.model.set_value(
frm.doc.doctype,
frm.doc.name,
@@ -920,10 +928,13 @@ erpnext.PointOfSale.ItemCart = class {
const me = this;
dfs.forEach((df) => {
this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({
df: { ...df, onchange: handle_customer_field_change },
df: df,
parent: $customer_form.find(`.${df.fieldname}-field`),
render_input: true,
});
this[`customer_${df.fieldname}_field`].$input?.on("blur", () => {
handle_customer_field_change.apply(this[`customer_${df.fieldname}_field`]);
});
this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]);
});

View File

@@ -315,8 +315,12 @@ erpnext.PointOfSale.ItemDetails = class {
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const field_control = this[`${fieldname}_control`];
const item_row_is_being_edited = this.compare_with_current_item(item_row);
if (item_row_is_being_edited && field_control && field_control.get_value() !== value) {
if (
item_row_is_being_edited &&
field_control &&
field_control.get_value() !== value &&
value == item_row[fieldname]
) {
field_control.set_value(value);
cur_pos.update_cart_html(item_row);
}

View File

@@ -118,6 +118,7 @@ erpnext.PointOfSale.ItemSelector = class {
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(uom)}"
data-rate="${escape(price_list_rate || 0)}"
data-stock-uom="${escape(item.stock_uom)}"
title="${item.item_name}">
${get_item_image_html()}
@@ -251,17 +252,19 @@ erpnext.PointOfSale.ItemSelector = class {
let serial_no = unescape($item.attr("data-serial-no"));
let uom = unescape($item.attr("data-uom"));
let rate = unescape($item.attr("data-rate"));
let stock_uom = unescape($item.attr("data-stock-uom"));
// escape(undefined) returns "undefined" then unescape returns "undefined"
batch_no = batch_no === "undefined" ? undefined : batch_no;
serial_no = serial_no === "undefined" ? undefined : serial_no;
uom = uom === "undefined" ? undefined : uom;
rate = rate === "undefined" ? undefined : rate;
stock_uom = stock_uom === "undefined" ? undefined : stock_uom;
me.events.item_selected({
field: "qty",
value: "+1",
item: { item_code, batch_no, serial_no, uom, rate },
item: { item_code, batch_no, serial_no, uom, rate, stock_uom },
});
me.search_field.set_focus();
});
@@ -325,13 +328,16 @@ erpnext.PointOfSale.ItemSelector = class {
}
filter_items({ search_term = "" } = {}) {
const selling_price_list = this.events.get_frm().doc.selling_price_list;
if (search_term) {
search_term = search_term.toLowerCase();
// memoize
this.search_index = this.search_index || {};
if (this.search_index[search_term]) {
const items = this.search_index[search_term];
this.search_index[selling_price_list] = this.search_index[selling_price_list] || {};
if (this.search_index[selling_price_list][search_term]) {
const items = this.search_index[selling_price_list][search_term];
this.items = items;
this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
@@ -343,7 +349,7 @@ erpnext.PointOfSale.ItemSelector = class {
// eslint-disable-next-line no-unused-vars
const { items, serial_no, batch_no, barcode } = message;
if (search_term && !barcode) {
this.search_index[search_term] = items;
this.search_index[selling_price_list][search_term] = items;
}
this.items = items;
this.render_item_list(items);

View File

@@ -235,7 +235,7 @@ erpnext.PointOfSale.Payment = class {
frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => {
// for setting correct amount after loyalty points are redeemed
const default_mop = locals[cdt][cdn];
const mode = default_mop.mode_of_payment.replace(/ +/g, "_").toLowerCase();
const mode = this.sanitize_mode_of_payment(default_mop.mode_of_payment);
if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) {
this[`${mode}_control`].set_value(default_mop.amount);
}
@@ -388,7 +388,7 @@ erpnext.PointOfSale.Payment = class {
this.$payment_modes.html(
`${payments
.map((p, i) => {
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
const payment_type = p.type;
const margin = i % 2 === 0 ? "pr-2" : "pl-2";
const amount = p.amount > 0 ? format_currency(p.amount, currency) : "";
@@ -407,7 +407,7 @@ erpnext.PointOfSale.Payment = class {
);
payments.forEach((p) => {
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
const me = this;
this[`${mode}_control`] = frappe.ui.form.make_control({
df: {
@@ -442,7 +442,7 @@ erpnext.PointOfSale.Payment = class {
const doc = this.events.get_frm().doc;
const payments = doc.payments;
payments.forEach((p) => {
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
if (p.default) {
setTimeout(() => {
this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
@@ -612,4 +612,12 @@ erpnext.PointOfSale.Payment = class {
toggle_component(show) {
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
}
sanitize_mode_of_payment(mode_of_payment) {
return mode_of_payment
.replace(/ +/g, "_")
.replace(/[^\p{L}\p{N}_-]/gu, "")
.replace(/^[^_a-zA-Z\p{L}]+/u, "")
.toLowerCase();
}
};

View File

@@ -32,7 +32,7 @@
"table_fieldname": "lost_reasons"
}
],
"modified": "2023-11-23 19:31:02.743353",
"modified": "2024-12-10 08:21:38.280627",
"modified_by": "Administrator",
"module": "Setup",
"name": "Quotation Lost Reason",
@@ -49,6 +49,22 @@
"role": "Sales Master Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Sales Manager"
},
{
"read": 1,
"role": "Maintenance User"
},
{
"read": 1,
"role": "Maintenance Manager"
}
],
"quick_entry": 1,

View File

@@ -4500,9 +4500,200 @@
},
"Sweden": {
"Sweden Tax": {
"account_name": "VAT",
"tax_rate": 25.00
"tax_categories": [],
"chart_of_accounts": {
"*": {
"sales_tax_templates": [
{
"title": "Försäljning Moms 25%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Utgående moms, 25 %",
"account_number": "2610",
"tax_rate": 25.00
},
"description": "Moms 25%",
"rate": 25.00
}
]
},
{
"title": "Försäljning Moms 12%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Utgående moms, 12 %",
"account_number": "2620",
"tax_rate": 12.00
},
"description": "Moms 12%",
"rate": 12.00
}
]
},
{
"title": "Försäljning Moms 6%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Utgående moms, 6 %",
"account_number": "2630",
"tax_rate": 6.00
},
"description": "Moms 6%",
"rate": 6.00
}
]
},
{
"title": "Försäljning Moms 0%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Utgående moms, 6 %",
"account_number": "2630",
"tax_rate": 0.00
},
"description": "Moms 0%",
"rate": 0.00
}
]
}
],
"purchase_tax_templates": [
{
"title": "Inköp Moms 25%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Ingående moms",
"account_number": "2640",
"root_type": "Liability",
"tax_rate": 25.00
},
"description": "Moms 25%",
"rate": 25.00
}
]
},
{
"title": "Inköp Moms 12%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Ingående moms",
"account_number": "2640",
"root_type": "Liability",
"tax_rate": 12.00
},
"description": "Moms 12%",
"rate": 12.00
}
]
},
{
"title": "Inköp Moms 6%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Ingående moms",
"account_number": "2640",
"root_type": "Liability",
"tax_rate": 6.00
},
"description": "Moms 6%",
"rate": 6.00
}
]
},
{
"title": "Inköp Moms 0%",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name": "Ingående moms",
"account_number": "2640",
"root_type": "Liability",
"tax_rate": 0.00
},
"description": "Moms 0%",
"rate": 0.00
}
]
}
],
"item_tax_templates": [
{
"title": "Artikel Moms 25%",
"taxes": [
{
"tax_type": {
"account_name": "Utgående moms, 25 %",
"account_number": "2610",
"root_type": "Liability",
"tax_rate": 25.00
},
"description": "Moms 25%",
"tax_rate": 25.00
}
]
},
{
"title": "Artikel Moms 12%",
"taxes": [
{
"tax_type": {
"account_name": "Utgående moms, 12 %",
"account_number": "2620",
"root_type": "Liability",
"tax_rate": 12.00
},
"description": "Moms 12%",
"tax_rate": 12.00
}
]
},
{
"title": "Artikel Moms 6%",
"taxes": [
{
"tax_type": {
"account_name": "Utgående moms, 6 %",
"account_number": "2630",
"root_type": "Liability",
"tax_rate": 6.00
},
"description": "Moms 6%",
"tax_rate": 6.00
}
]
},
{
"title": "Artikel Moms 0%",
"taxes": [
{
"tax_type": {
"account_name": "Utgående moms, 0 %",
"account_number": "2611",
"root_type": "Liability",
"tax_rate": 0.00
},
"description": "Moms 0%",
"tax_rate": 0.00
}
]
}
]
}
}
},

View File

@@ -292,7 +292,7 @@ def get_or_create_tax_group(company_name, root_type):
tax_group_account.flags.ignore_links = True
tax_group_account.flags.ignore_validate = True
tax_group_account.insert(ignore_permissions=True)
tax_group_account.insert(ignore_permissions=True, ignore_if_duplicate=True)
tax_group_name = tax_group_account.name

View File

@@ -1,19 +1,37 @@
{
"chart_name": "Warehouse wise Stock Value",
"chart_type": "Custom",
"creation": "2020-07-20 21:01:04.296157",
"creation": "2022-03-30 00:58:02.018824",
"docstatus": 0,
"doctype": "Dashboard Chart",
"filters_json": "{}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 13:01:01.815123",
"last_synced_on": "2024-12-23 18:44:46.822164",
"modified": "2024-12-23 19:31:17.003946",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse wise Stock Value",
"number_of_groups": 0,
"owner": "Administrator",
"roles": [
{
"role": "Sales Manager"
},
{
"role": "Accounts Manager"
},
{
"role": "Stock Manager"
},
{
"role": "Stock User"
},
{
"role": "Accounts User"
}
],
"source": "Warehouse wise Stock Value",
"timeseries": 0,
"type": "Bar",

View File

@@ -3,7 +3,7 @@ from collections import defaultdict
import frappe
from frappe.query_builder.functions import CombineDatetime, Sum
from frappe.utils import flt
from frappe.utils import flt, nowtime
from frappe.utils.deprecations import deprecated
from pypika import Order
@@ -112,7 +112,10 @@ class DeprecatedBatchNoValuation:
sle = frappe.qb.DocType("Stock Ledger Entry")
timestamp_condition = None
if self.sle.posting_date and self.sle.posting_time:
if self.sle.posting_date:
if self.sle.posting_time is None:
self.sle.posting_time = nowtime()
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
if not self.sle.creation:
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
@@ -178,6 +181,9 @@ class DeprecatedBatchNoValuation:
stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty
self.stock_value_change += stock_value_change
self.non_batchwise_balance_value[batch_no] -= stock_value_change
self.non_batchwise_balance_qty[batch_no] -= ledger.qty
frappe.db.set_value(
"Serial and Batch Entry",
ledger.name,
@@ -217,7 +223,6 @@ class DeprecatedBatchNoValuation:
.select(
sle.batch_no,
Sum(sle.actual_qty).as_("batch_qty"),
Sum(sle.stock_value_difference).as_("batch_value"),
)
.where(
(sle.item_code == self.sle.item_code)
@@ -234,11 +239,59 @@ class DeprecatedBatchNoValuation:
if self.sle.name:
query = query.where(sle.name != self.sle.name)
for d in query.run(as_dict=True):
self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value)
self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty)
batch_data = query.run(as_dict=True)
for d in batch_data:
self.available_qty[d.batch_no] += flt(d.batch_qty)
last_sle = self.get_last_sle_for_non_batch()
for d in batch_data:
self.non_batchwise_balance_value[d.batch_no] += flt(last_sle.stock_value)
self.non_batchwise_balance_qty[d.batch_no] += flt(last_sle.qty_after_transaction)
def get_last_sle_for_non_batch(self):
from erpnext.stock.utils import get_combine_datetime
sle = frappe.qb.DocType("Stock Ledger Entry")
batch = frappe.qb.DocType("Batch")
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
if not self.sle.creation:
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
timestamp_condition = sle.posting_datetime < posting_datetime
if self.sle.creation:
timestamp_condition |= (sle.posting_datetime == posting_datetime) & (
sle.creation < self.sle.creation
)
query = (
frappe.qb.from_(sle)
.inner_join(batch)
.on(sle.batch_no == batch.name)
.select(
sle.stock_value,
sle.qty_after_transaction,
)
.where(
(sle.item_code == self.sle.item_code)
& (sle.warehouse == self.sle.warehouse)
& (sle.batch_no.isnotnull())
& (batch.use_batchwise_valuation == 0)
& (sle.is_cancelled == 0)
)
.where(timestamp_condition)
.orderby(sle.posting_datetime, order=Order.desc)
.orderby(sle.creation, order=Order.desc)
.limit(1)
)
if self.sle.name:
query = query.where(sle.name != self.sle.name)
data = query.run(as_dict=True)
return data[0] if data else {}
@deprecated
def set_balance_value_from_bundle(self) -> None:
bundle = frappe.qb.DocType("Serial and Batch Bundle")

View File

@@ -113,7 +113,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-05-18 11:46:04.448220",
"modified": "2024-12-19 13:48:46.618066",
"modified_by": "Administrator",
"module": "Stock",
"name": "Closing Stock Balance",
@@ -121,6 +121,7 @@
"owner": "Administrator",
"permissions": [
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -130,10 +131,39 @@
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -65,7 +65,7 @@ class ClosingStockBalance(Document):
& (
(table.from_date.between(self.from_date, self.to_date))
| (table.to_date.between(self.from_date, self.to_date))
| ((table.from_date >= self.from_date) & (table.to_date >= self.to_date))
| ((self.from_date >= table.from_date) & (table.from_date >= self.to_date))
)
)
)

View File

@@ -237,8 +237,13 @@ class InventoryDimension(Document):
custom_fields["Stock Ledger Entry"] = dimension_field
filter_custom_fields = {}
ignore_doctypes = ["Serial and Batch Bundle", "Serial and Batch Entry", "Pick List Item"]
if custom_fields:
for doctype, fields in custom_fields.items():
if doctype in ignore_doctypes:
continue
if isinstance(fields, dict):
fields = [fields]

View File

@@ -322,7 +322,7 @@ frappe.ui.form.on("Material Request", {
default: 1,
},
],
primary_action_label: "Get Items",
primary_action_label: __("Get Items"),
primary_action(values) {
if (!values) return;
values["company"] = frm.doc.company;

View File

@@ -766,6 +766,7 @@ def raise_work_orders(material_request):
)
wo_order.set_work_order_operations()
wo_order.flags.ignore_validate = True
wo_order.flags.ignore_mandatory = True
wo_order.save()

View File

@@ -14,6 +14,12 @@ frappe.listview_settings["Material Request"] = {
}
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 0) {
return [__("Pending"), "orange", "per_ordered,=,0"];
} else if (
doc.docstatus == 1 &&
flt(doc.per_ordered, precision) < 100 &&
doc.material_request_type == "Material Transfer"
) {
return [__("Partially Received"), "yellow", "per_ordered,<,100"];
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) < 100) {
return [__("Partially ordered"), "yellow", "per_ordered,<,100"];
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 100) {

View File

@@ -17,6 +17,7 @@ from erpnext.stock.doctype.material_request.material_request import (
make_supplier_quotation,
raise_work_orders,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestMaterialRequest(FrappeTestCase):
@@ -59,6 +60,43 @@ class TestMaterialRequest(FrappeTestCase):
self.assertEqual(se.doctype, "Stock Entry")
self.assertEqual(len(se.get("items")), len(mr.get("items")))
def test_partial_make_stock_entry(self):
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry as _make_stock_entry
mr = frappe.copy_doc(test_records[0]).insert()
source_wh = create_warehouse(
warehouse_name="_Test Source Warehouse",
properties={"parent_warehouse": "All Warehouses - _TC"},
company="_Test Company",
)
mr = frappe.get_doc("Material Request", mr.name)
mr.material_request_type = "Material Transfer"
for row in mr.items:
_make_stock_entry(
item_code=row.item_code,
qty=10,
to_warehouse=source_wh,
company="_Test Company",
rate=100,
)
row.from_warehouse = source_wh
row.qty = 10
mr.save()
mr.submit()
se = make_stock_entry(mr.name)
se.get("items")[0].qty = 5
se.insert()
se.submit()
mr.reload()
self.assertEqual(mr.status, "Partially Received")
def test_in_transit_make_stock_entry(self):
mr = frappe.copy_doc(test_records[0]).insert()

View File

@@ -1247,6 +1247,7 @@ def create_stock_entry(pick_list):
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.pick_list = pick_list.get("name")
stock_entry.purpose = pick_list.get("purpose")
stock_entry.company = pick_list.get("company")
stock_entry.set_stock_entry_type()
if pick_list.get("work_order"):

View File

@@ -920,12 +920,17 @@ class PurchaseReceipt(BuyingController):
)
def enable_recalculate_rate_in_sles(self):
rejected_warehouses = frappe.get_all(
"Purchase Receipt Item", filters={"parent": self.name}, pluck="rejected_warehouse"
)
sle_table = frappe.qb.DocType("Stock Ledger Entry")
(
frappe.qb.update(sle_table)
.set(sle_table.recalculate_rate, 1)
.where(sle_table.voucher_no == self.name)
.where(sle_table.voucher_type == "Purchase Receipt")
.where(sle_table.warehouse.notin(rejected_warehouses))
).run()
@@ -1178,6 +1183,9 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
return pending_qty, 0
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
if item_row.rejected_qty and returned_qty:
returned_qty -= item_row.rejected_qty
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0

View File

@@ -15,6 +15,7 @@
"inspection_type",
"reference_type",
"reference_name",
"child_row_reference",
"section_break_7",
"item_code",
"item_serial_no",
@@ -238,6 +239,15 @@
"fieldname": "manual_inspection",
"fieldtype": "Check",
"label": "Manual Inspection"
},
{
"fieldname": "child_row_reference",
"fieldtype": "Data",
"hidden": 1,
"label": "Child Row Reference",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-search",
@@ -245,7 +255,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-08-23 11:56:50.282878",
"modified": "2024-12-30 19:08:16.611192",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
@@ -272,4 +282,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"states": []
}
}

View File

@@ -29,6 +29,7 @@ class QualityInspection(Document):
amended_from: DF.Link | None
batch_no: DF.Link | None
bom_no: DF.Link | None
child_row_reference: DF.Data | None
description: DF.SmallText | None
inspected_by: DF.Link
inspection_type: DF.Literal["", "Incoming", "Outgoing", "In Process"]
@@ -74,6 +75,64 @@ class QualityInspection(Document):
self.inspect_and_set_status()
self.validate_inspection_required()
self.set_child_row_reference()
def set_child_row_reference(self):
if self.child_row_reference:
return
if not (self.reference_type and self.reference_name):
return
doctype = self.reference_type + " Item"
if self.reference_type == "Stock Entry":
doctype = "Stock Entry Detail"
child_row_references = frappe.get_all(
doctype,
filters={"parent": self.reference_name, "item_code": self.item_code},
pluck="name",
)
if not child_row_references:
return
if len(child_row_references) == 1:
self.child_row_reference = child_row_references[0]
else:
self.distribute_child_row_reference(child_row_references)
def distribute_child_row_reference(self, child_row_references):
quality_inspections = frappe.get_all(
"Quality Inspection",
filters={
"reference_name": self.reference_name,
"item_code": self.item_code,
"docstatus": ("<", 2),
},
fields=["name", "child_row_reference", "docstatus"],
order_by="child_row_reference desc",
)
for row in quality_inspections:
if not child_row_references:
break
if row.child_row_reference and row.child_row_reference in child_row_references:
child_row_references.remove(row.child_row_reference)
continue
if row.docstatus == 1:
continue
if row.name == self.name:
self.child_row_reference = child_row_references[0]
else:
frappe.db.set_value(
"Quality Inspection", row.name, "child_row_reference", child_row_references[0]
)
child_row_references.remove(child_row_references[0])
def validate_inspection_required(self):
if self.reference_type in ["Purchase Receipt", "Purchase Invoice"] and not frappe.get_cached_value(
@@ -157,35 +216,38 @@ class QualityInspection(Document):
)
else:
args = [quality_inspection, self.modified, self.reference_name, self.item_code]
doctype = self.reference_type + " Item"
if self.reference_type == "Stock Entry":
doctype = "Stock Entry Detail"
if self.reference_type and self.reference_name:
conditions = ""
if doctype and self.reference_name:
child_doc = frappe.qb.DocType(doctype)
query = (
frappe.qb.update(child_doc)
.set(child_doc.quality_inspection, quality_inspection)
.where(
(child_doc.parent == self.reference_name) & (child_doc.item_code == self.item_code)
)
)
if self.batch_no and self.docstatus == 1:
conditions += " and t1.batch_no = %s"
args.append(self.batch_no)
query = query.where(child_doc.batch_no == self.batch_no)
if self.docstatus == 2: # if cancel, then remove qi link wherever same name
conditions += " and t1.quality_inspection = %s"
args.append(self.name)
query = query.where(child_doc.quality_inspection == self.name)
frappe.db.sql(
f"""
UPDATE
`tab{doctype}` t1, `tab{self.reference_type}` t2
SET
t1.quality_inspection = %s, t2.modified = %s
WHERE
t1.parent = %s
and t1.item_code = %s
and t1.parent = t2.name
{conditions}
""",
args,
if self.child_row_reference:
query = query.where(child_doc.name == self.child_row_reference)
query.run()
frappe.db.set_value(
self.reference_type,
self.reference_name,
"modified",
self.modified,
)
def inspect_and_set_status(self):

View File

@@ -90,12 +90,10 @@ class SerialandBatchBundle(Document):
self.validate_duplicate_serial_and_batch_no()
self.validate_voucher_no()
if self.docstatus == 0:
self.allow_existing_serial_nos()
if self.type_of_transaction == "Maintenance":
return
self.allow_existing_serial_nos()
if not self.flags.ignore_validate_serial_batch or frappe.flags.in_test:
self.validate_serial_nos_duplicate()
self.check_future_entries_exists()
@@ -809,7 +807,7 @@ class SerialandBatchBundle(Document):
)
for serial_no, batch_no in serial_batches.items():
if correct_batches.get(serial_no) != batch_no:
if correct_batches.get(serial_no) and correct_batches.get(serial_no) != batch_no:
self.throw_error_message(
f"Serial No {bold(serial_no)} does not belong to Batch No {bold(batch_no)}"
)
@@ -1188,19 +1186,19 @@ def parse_csv_file_to_get_serial_batch(reader):
continue
if has_serial_no or (has_serial_no and has_batch_no):
_dict = {"serial_no": row[0], "qty": 1}
_dict = {"serial_no": row[0].strip(), "qty": 1}
if has_batch_no:
_dict.update(
{
"batch_no": row[1],
"batch_no": row[1].strip(),
"qty": row[2],
}
)
batch_nos.append(
{
"batch_no": row[1],
"batch_no": row[1].strip(),
"qty": row[2],
}
)
@@ -1209,7 +1207,7 @@ def parse_csv_file_to_get_serial_batch(reader):
elif has_batch_no:
batch_nos.append(
{
"batch_no": row[0],
"batch_no": row[0].strip(),
"qty": row[1],
}
)
@@ -1253,7 +1251,7 @@ def make_serial_nos(item_code, serial_nos):
"Item", item_code, ["description", "item_code", "item_name", "warranty_period"], as_dict=1
)
serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
serial_nos = [d.get("serial_no").strip() for d in serial_nos if d.get("serial_no")]
existing_serial_nos = frappe.get_all("Serial No", filters={"name": ("in", serial_nos)})
existing_serial_nos = [d.get("name") for d in existing_serial_nos if d.get("name")]
@@ -2101,6 +2099,8 @@ def update_available_batches(available_batches, *reserved_batches) -> None:
def get_available_batches(kwargs):
from erpnext.stock.utils import get_combine_datetime
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
batch_table = frappe.qb.DocType("Batch")
@@ -2128,9 +2128,9 @@ def get_available_batches(kwargs):
if kwargs.get("posting_time") is None:
kwargs.posting_time = nowtime()
timestamp_condition = CombineDatetime(
stock_ledger_entry.posting_date, stock_ledger_entry.posting_time
) <= CombineDatetime(kwargs.posting_date, kwargs.posting_time)
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
kwargs.posting_date, kwargs.posting_time
)
query = query.where(timestamp_condition)

View File

@@ -166,7 +166,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
for qty, valuation in {10: 100, 20: 200}.items():
stock_queue.append([qty, valuation])
qty_after_transaction += qty
balance_value += qty_after_transaction * valuation
balance_value += qty * valuation
doc = frappe.get_doc(
{
@@ -177,6 +177,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
"incoming_rate": valuation,
"qty_after_transaction": qty_after_transaction,
"stock_value_difference": valuation * qty,
"stock_value": balance_value,
"balance_value": balance_value,
"valuation_rate": balance_value / qty_after_transaction,
"actual_qty": qty,
@@ -186,6 +187,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
}
)
doc.set_posting_datetime()
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.flags.ignore_links = True
@@ -586,6 +588,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
"company": "_Test Company",
}
)
doc.set_posting_datetime()
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.flags.ignore_links = True

View File

@@ -371,6 +371,7 @@ frappe.ui.form.on("Stock Entry", {
function () {
frappe.call({
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_expired_batch_items",
freeze: true,
callback: function (r) {
if (!r.exc && r.message) {
frm.set_value("items", []);

View File

@@ -201,7 +201,6 @@ class StockEntry(StockController):
self.validate_purpose()
self.validate_item()
self.validate_customer_provided_item()
self.validate_qty()
self.set_transfer_qty()
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "transfer_qty")
@@ -232,7 +231,7 @@ class StockEntry(StockController):
self.validate_serialized_batch()
self.calculate_rate_and_amount()
self.validate_putaway_capacity()
self.validate_component_quantities()
self.validate_component_and_quantities()
if not self.get("purpose") == "Manufacture":
# ignore scrap item wh difference and empty source/target wh
@@ -463,40 +462,6 @@ class StockEntry(StockController):
flt(item.qty) * flt(item.conversion_factor), self.precision("transfer_qty", item)
)
def validate_qty(self):
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
if self.purpose in manufacture_purpose and self.work_order:
if not frappe.get_value("Work Order", self.work_order, "skip_transfer"):
item_code = []
for item in self.items:
if cstr(item.t_warehouse) == "":
req_items = frappe.get_all(
"Work Order Item",
filters={"parent": self.work_order, "item_code": item.item_code},
fields=["item_code"],
)
transferred_materials = frappe.db.sql(
"""
select
sum(sed.qty) as qty
from `tabStock Entry` se,`tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1 and
(se.purpose='Material Transfer for Manufacture' or se.purpose='Manufacture')
and sed.item_code=%s and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
""",
(item.item_code, self.work_order),
as_dict=1,
)
stock_qty = flt(item.qty)
trans_qty = flt(transferred_materials[0].qty)
if req_items:
if stock_qty > trans_qty:
item_code.append(item.item_code)
def validate_fg_completed_qty(self):
item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order:
@@ -748,7 +713,7 @@ class StockEntry(StockController):
title=_("Insufficient Stock"),
)
def validate_component_quantities(self):
def validate_component_and_quantities(self):
if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]:
return
@@ -761,20 +726,31 @@ class StockEntry(StockController):
raw_materials = self.get_bom_raw_materials(self.fg_completed_qty)
precision = frappe.get_precision("Stock Entry Detail", "qty")
for row in self.items:
if not row.s_warehouse:
continue
if details := raw_materials.get(row.item_code):
if flt(details.get("qty"), precision) != flt(row.qty, precision):
for item_code, details in raw_materials.items():
if matched_item := self.get_matched_items(item_code):
if flt(details.get("qty"), precision) != flt(matched_item.qty, precision):
frappe.throw(
_("For the item {0}, the quantity should be {1} according to the BOM {2}.").format(
frappe.bold(row.item_code),
flt(details.get("qty"), precision),
frappe.bold(item_code),
flt(details.get("qty")),
get_link_to_form("BOM", self.bom_no),
),
title=_("Incorrect Component Quantity"),
)
else:
frappe.throw(
_("According to the BOM {0}, the Item '{1}' is missing in the stock entry.").format(
get_link_to_form("BOM", self.bom_no), frappe.bold(item_code)
),
title=_("Missing Item"),
)
def get_matched_items(self, item_code):
for row in self.items:
if row.item_code == item_code:
return row
return {}
@frappe.whitelist()
def get_stock_and_rate(self):
@@ -2975,17 +2951,45 @@ def get_uom_details(item_code, uom, qty):
@frappe.whitelist()
def get_expired_batch_items():
return frappe.db.sql(
"""select b.item, sum(sle.actual_qty) as qty, sle.batch_no, sle.warehouse, sle.stock_uom\
from `tabBatch` b, `tabStock Ledger Entry` sle
where b.expiry_date <= %s
and b.expiry_date is not NULL
and b.batch_id = sle.batch_no and sle.is_cancelled = 0
group by sle.warehouse, sle.item_code, sle.batch_no""",
(nowdate()),
as_dict=1,
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos
expired_batches = get_expired_batches()
if not expired_batches:
return []
expired_batches_stock = get_auto_batch_nos(
frappe._dict(
{
"batch_no": list(expired_batches.keys()),
"for_stock_levels": True,
}
)
)
for row in expired_batches_stock:
row.update(expired_batches.get(row.batch_no))
return expired_batches_stock
def get_expired_batches():
batch = frappe.qb.DocType("Batch")
data = (
frappe.qb.from_(batch)
.select(batch.item, batch.name.as_("batch_no"), batch.stock_uom)
.where((batch.expiry_date <= nowdate()) & (batch.expiry_date.isnotnull()))
).run(as_dict=True)
if not data:
return []
expired_batches = frappe._dict()
for row in data:
expired_batches[row.batch_no] = row
return expired_batches
@frappe.whitelist()
def get_warehouse_details(args):
@@ -3261,8 +3265,10 @@ def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=N
doc.append("entries", {"serial_no": serial_no, "warehouse": row.warehouse, "qty": -1})
elif row.batches_to_be_consume:
precision = frappe.get_precision("Serial and Batch Entry", "qty")
doc.has_batch_no = 1
for batch_no, qty in row.batches_to_be_consume.items():
qty = flt(qty, precision)
doc.append("entries", {"batch_no": batch_no, "warehouse": row.warehouse, "qty": qty * -1})
if not doc.entries:

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