Compare commits

..

43 Commits

Author SHA1 Message Date
Frappe PR Bot
6e479f918d chore(release): Bumped to Version 14.57.0
# [14.57.0](https://github.com/frappe/erpnext/compare/v14.56.0...v14.57.0) (2023-12-27)

### Bug Fixes

* allow to set rate manually for service item in BOM ([#38880](https://github.com/frappe/erpnext/issues/38880)) ([c2f692a](c2f692a4e4))
* display all item rate stop messages ([226d0e0](226d0e0196))
* do not reset the basic rate for the material receipt stock entry ([#38896](https://github.com/frappe/erpnext/issues/38896)) ([98bfcc4](98bfcc4c75))
* first name error on customer creation ([#38927](https://github.com/frappe/erpnext/issues/38927)) ([28f052d](28f052d586))
* german translations for Lost Quotations ([#38435](https://github.com/frappe/erpnext/issues/38435)) ([81b5e6c](81b5e6c5f1))
* groups for current accounts in German CoAs ([39aa36e](39aa36e44b))
* incorrect price list in customer-wise item price report ([7577706](7577706a9d))
* min order qty optional in production plan (backport [#38956](https://github.com/frappe/erpnext/issues/38956)) ([#38957](https://github.com/frappe/erpnext/issues/38957)) ([f6f2712](f6f2712db0))
* remove rows with zero consumed qty ([f2f4100](f2f410093a))
* reposting not fixing valuation rate for sales return using movin… ([#38895](https://github.com/frappe/erpnext/issues/38895)) ([3a668bb](3a668bbe96))
* reset the incoming rate on changing of the warehouse ([#38909](https://github.com/frappe/erpnext/issues/38909)) ([2770ca1](2770ca1b65))
* skip jvs against bank accounts ([98198d3](98198d35ea))
* **test:** expect account currency when party account is specified. ([30cb218](30cb218638))
* unset discount amount from coupon discounts ([87e8dd0](87e8dd0d63))
* use party account currency when party account is specified ([d9e0d55](d9e0d55b88))
* **ux:** make PR and PI Item rate field readonly based on `Maintain Same Rate` (backport [#38942](https://github.com/frappe/erpnext/issues/38942)) ([#38943](https://github.com/frappe/erpnext/issues/38943)) ([1eee203](1eee203f7f))

### Features

* in_party_currency option for AR/AP reports ([3c717db](3c717db3fd))
* total_asset_cost field (backport [#38879](https://github.com/frappe/erpnext/issues/38879)) ([#38886](https://github.com/frappe/erpnext/issues/38886)) ([8169c7d](8169c7de0d))

### Performance Improvements

* Drop unused/duplicate/sub-optimal indexes (backport [#38884](https://github.com/frappe/erpnext/issues/38884)) ([#38912](https://github.com/frappe/erpnext/issues/38912)) ([b254a72](b254a72d41))
* use estimated rows instead of actual rows (backport [#38830](https://github.com/frappe/erpnext/issues/38830)) ([#38875](https://github.com/frappe/erpnext/issues/38875)) ([4d1ccd9](4d1ccd9e27))
2023-12-27 08:40:26 +00:00
rohitwaghchaure
8e8d0ad9aa Merge pull request #38953 from frappe/version-14-hotfix
chore: release v14
2023-12-27 14:09:17 +05:30
rohitwaghchaure
400c78c600 Merge branch 'version-14' into version-14-hotfix 2023-12-27 10:24:01 +05:30
mergify[bot]
f6f2712db0 fix: min order qty optional in production plan (backport #38956) (#38957)
* fix: min order qty optional in production plan (#38956)

* fix: min order qty optional in production plan

* fix: test cases

(cherry picked from commit b09c9354fb)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/bom.json
#	erpnext/manufacturing/doctype/production_plan/production_plan.py

* chore: fix conflicts

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-12-26 21:59:09 +05:30
mergify[bot]
1eee203f7f fix(ux): make PR and PI Item rate field readonly based on Maintain Same Rate (backport #38942) (#38943)
* fix(ux): make PI Item rate field editable

(cherry picked from commit eb5bb9f9a9)

* fix(ux): make PI Item rate field readonly based on `Maintain Same Rate`

(cherry picked from commit cb9114442b)

* fix(ux): make PR Item rate field readonly based on `Maintain Same Rate`

(cherry picked from commit b1ba210332)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-12-26 14:45:54 +05:30
ruthra kumar
ec337a1c9f Merge pull request #38945 from frappe/mergify/bp/version-14-hotfix/pr-38891
fix: incorrect price list in customer-wise item price report (backport #38891)
2023-12-26 12:16:52 +05:30
ruthra kumar
7577706a9d fix: incorrect price list in customer-wise item price report
(cherry picked from commit 9a00edb031)
2023-12-26 06:17:45 +00:00
mergify[bot]
81b5e6c5f1 fix: german translations for Lost Quotations (#38435)
* fix: german translations for Lost Quotations (#38435)

(cherry picked from commit 5952cfa673)

# Conflicts:
#	erpnext/translations/de.csv

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-12-24 16:24:31 +05:30
ruthra kumar
28f052d586 fix: first name error on customer creation (#38927)
fix: error on customer creation
2023-12-24 16:12:13 +05:30
s-aga-r
2eac618bba Merge pull request #38892 from s-aga-r/FIX-6565
fix: remove rows with zero consumed qty
2023-12-22 15:58:09 +05:30
Deepesh Garg
6f6de13813 Merge pull request #38815 from frappe/mergify/bp/version-14-hotfix/pr-38803
fix: groups for current accounts in German CoAs (#38803)
2023-12-22 08:40:28 +05:30
Deepesh Garg
be1a269f9f Merge pull request #38857 from frappe/revert-38422-mergify/bp/version-14-hotfix/pr-38250
Revert "fix: unset discount amount based on coupon code (backport #38250)"
2023-12-22 08:40:07 +05:30
Deepesh Garg
0d70be56d2 Merge pull request #38370 from pps190/backport/version-14-hotfix/38289
fix: display all item rate stop messages (#38289)
2023-12-22 08:38:19 +05:30
Deepesh Garg
50655821c1 Merge pull request #38770 from frappe/mergify/bp/version-14-hotfix/pr-38767
fix: skip JVs against bank accounts in tax report (#38767)
2023-12-22 08:37:41 +05:30
mergify[bot]
b254a72d41 perf: Drop unused/duplicate/sub-optimal indexes (backport #38884) (#38912)
* perf: Drop unused/duplicate/sub-optimal indexes (#38884)

* ci: enable more checks

* perf: Drop unused/duplicate indexes

(cherry picked from commit 787333896c)

# Conflicts:
#	erpnext/accounts/doctype/gl_entry/gl_entry.json
#	erpnext/patches.txt

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-12-21 21:40:59 +05:30
rohitwaghchaure
2770ca1b65 fix: reset the incoming rate on changing of the warehouse (#38909) 2023-12-21 18:49:36 +05:30
mergify[bot]
0d7cb1a9be chore: additional_asset_cost field (backport #38904) (backport #38905) (#38908)
chore: additional_asset_cost field (backport #38904) (#38905)

* chore: additional_asset_cost field (#38904)

(cherry picked from commit 283763dfb2)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py

* chore: resolve conflicts in asset.py

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit 3b9feffc00)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-21 17:12:01 +05:30
Frappe PR Bot
3abb91b2c4 chore(release): Bumped to Version 14.56.0
# [14.56.0](https://github.com/frappe/erpnext/compare/v14.55.2...v14.56.0) (2023-12-21)

### Features

* total_asset_cost field (backport [#38879](https://github.com/frappe/erpnext/issues/38879)) (backport [#38886](https://github.com/frappe/erpnext/issues/38886)) ([#38907](https://github.com/frappe/erpnext/issues/38907)) ([a63b8df](a63b8df867))
2023-12-21 11:41:51 +00:00
mergify[bot]
a63b8df867 feat: total_asset_cost field (backport #38879) (backport #38886) (#38907)
feat: total_asset_cost field (backport #38879) (#38886)

* feat: total_asset_cost field (#38879)

(cherry picked from commit d370c60a6c)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.json
#	erpnext/assets/doctype/asset/asset.py
#	erpnext/patches.txt

* chore: resolve conflicts in asset.json

* chore: resolve conflicts in asset.py

* chore: resolve conflicts in patches.txt

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit 8169c7de0d)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-21 17:10:04 +05:30
mergify[bot]
3b9feffc00 chore: additional_asset_cost field (backport #38904) (#38905)
* chore: additional_asset_cost field (#38904)

(cherry picked from commit 283763dfb2)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py

* chore: resolve conflicts in asset.py

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-12-21 16:56:32 +05:30
Frappe PR Bot
0c13cb829c chore(release): Bumped to Version 14.55.2
## [14.55.2](https://github.com/frappe/erpnext/compare/v14.55.1...v14.55.2) (2023-12-21)

### Bug Fixes

* reposting not fixing valuation rate for sales return using movin… (backport [#38895](https://github.com/frappe/erpnext/issues/38895)) ([#38900](https://github.com/frappe/erpnext/issues/38900)) ([8aba707](8aba707b6a))
2023-12-21 10:05:58 +00:00
mergify[bot]
8aba707b6a fix: reposting not fixing valuation rate for sales return using movin… (backport #38895) (#38900)
fix: reposting not fixing valuation rate for sales return using movin… (#38895)

fix: reposting not fixing valuation rate for sales return using moving average method
(cherry picked from commit 3a668bbe96)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-12-21 15:34:49 +05:30
rohitwaghchaure
98bfcc4c75 fix: do not reset the basic rate for the material receipt stock entry (#38896) 2023-12-21 14:13:55 +05:30
rohitwaghchaure
3a668bbe96 fix: reposting not fixing valuation rate for sales return using movin… (#38895)
fix: reposting not fixing valuation rate for sales return using moving average method
2023-12-21 14:13:34 +05:30
s-aga-r
f2f410093a fix: remove rows with zero consumed qty 2023-12-21 11:03:18 +05:30
mergify[bot]
8169c7de0d feat: total_asset_cost field (backport #38879) (#38886)
* feat: total_asset_cost field (#38879)

(cherry picked from commit d370c60a6c)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.json
#	erpnext/assets/doctype/asset/asset.py
#	erpnext/patches.txt

* chore: resolve conflicts in asset.json

* chore: resolve conflicts in asset.py

* chore: resolve conflicts in patches.txt

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-12-20 22:57:37 +05:30
Frappe PR Bot
eefac19cc8 chore(release): Bumped to Version 14.55.1
## [14.55.1](https://github.com/frappe/erpnext/compare/v14.55.0...v14.55.1) (2023-12-20)

### Bug Fixes

* allow to set rate manually for service item in BOM (backport [#38880](https://github.com/frappe/erpnext/issues/38880)) ([#38881](https://github.com/frappe/erpnext/issues/38881)) ([afc0491](afc049154d))
2023-12-20 13:06:21 +00:00
mergify[bot]
afc049154d fix: allow to set rate manually for service item in BOM (backport #38880) (#38881)
fix: allow to set rate manually for service item in BOM (#38880)

(cherry picked from commit c2f692a4e4)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-12-20 18:34:31 +05:30
mergify[bot]
4d1ccd9e27 perf: use estimated rows instead of actual rows (backport #38830) (#38875)
* perf: use estimated rows instead of actual rows (#38830)

(cherry picked from commit 9983283f95)

# Conflicts:
#	erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-12-20 18:17:41 +05:30
Deepesh Garg
c48c8b8ddb Merge pull request #38858 from GursheenK/unset-discount-amt-coupon-code
fix: unset discount amount for removed coupons
2023-12-20 18:17:20 +05:30
rohitwaghchaure
c2f692a4e4 fix: allow to set rate manually for service item in BOM (#38880) 2023-12-20 17:53:48 +05:30
ruthra kumar
d94b5a318d Merge pull request #38872 from frappe/mergify/bp/version-14-hotfix/pr-38838
fix: use party account currency when party account is specified (backport #38838)
2023-12-20 13:09:17 +05:30
Devin Slauenwhite
30cb218638 fix(test): expect account currency when party account is specified.
(cherry picked from commit a09241e3c7)
2023-12-20 07:05:27 +00:00
Devin Slauenwhite
d9e0d55b88 fix: use party account currency when party account is specified
(cherry picked from commit c7b961ffa2)
2023-12-20 07:05:26 +00:00
ruthra kumar
1e9b4e2403 Merge pull request #38800 from pps190/backport/version-14-hotfix/37746
feat: in_party_currency option for AR/AP reports (backport #37746)
2023-12-20 12:25:35 +05:30
Gursheen Anand
87e8dd0d63 fix: unset discount amount from coupon discounts 2023-12-19 15:56:59 +05:30
Gursheen Kaur Anand
37a18299d0 Revert "fix: unset discount amount based on coupon code (backport #38250)" 2023-12-19 15:35:35 +05:30
Devin Slauenwhite
226d0e0196 fix: display all item rate stop messages 2023-12-18 14:14:29 -05:00
barredterra
39aa36e44b fix: groups for current accounts in German CoAs
(cherry picked from commit 259f313af7)
2023-12-17 18:24:10 +00:00
Devin Slauenwhite
7eee573fcb fixup! feat: in_party_currency option for AR/AP reports
fix: merge conflict typo
2023-12-16 16:29:01 +00:00
Richard Case
0894022695 chore: update tests 2023-12-16 16:15:00 +00:00
Richard Case
3c717db3fd feat: in_party_currency option for AR/AP reports 2023-12-16 16:14:44 +00:00
Gursheen Anand
98198d35ea fix: skip jvs against bank accounts
(cherry picked from commit f7b2380ec1)
2023-12-15 07:29:14 +00:00
47 changed files with 465 additions and 520 deletions

View File

@@ -5,7 +5,7 @@ fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.3.0
hooks:
- id: trailing-whitespace
files: "erpnext.*"
@@ -15,6 +15,10 @@ repos:
args: ['--branch', 'develop']
- id: check-merge-conflict
- id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4

View File

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

View File

@@ -53,8 +53,13 @@
},
"II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1,
"Ford. a. Lieferungen und Leistungen": {
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "1400",
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1410",
"account_type": "Receivable"
},
"Durchlaufende Posten": {
@@ -180,8 +185,13 @@
},
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1,
"Verbindlichkeiten aus Lieferungen u. Leistungen": {
"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "1600",
"account_type": "Payable",
"is_group": 1
},
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1610",
"account_type": "Payable"
}
},

View File

@@ -407,13 +407,10 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960"
},
"Debitoren": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": {
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "1200",
"account_type": "Receivable"
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1210"
@@ -1138,18 +1135,15 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964"
},
"Kreditoren": {
"account_number": "70000",
"Verb. aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "3300",
"account_type": "Payable",
"is_group": 1,
"Wareneingangs-­Verrechnungskonto" : {
"Wareneingangs-Verrechnungskonto" : {
"account_number": "70001",
"account_type": "Stock Received But Not Billed"
}
},
"Verb. aus Lieferungen und Leistungen": {
"account_number": "3300",
"account_type": "Payable"
},
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "3310"
},

View File

@@ -138,8 +138,7 @@
"label": "Against Voucher Type",
"oldfieldname": "against_voucher_type",
"oldfieldtype": "Data",
"options": "DocType",
"search_index": 1
"options": "DocType"
},
{
"fieldname": "against_voucher",
@@ -158,8 +157,7 @@
"label": "Voucher Type",
"oldfieldname": "voucher_type",
"oldfieldtype": "Select",
"options": "DocType",
"search_index": 1
"options": "DocType"
},
{
"fieldname": "voucher_no",
@@ -291,4 +289,4 @@
"search_fields": "voucher_no,account,posting_date,against_voucher",
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -164,6 +164,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
})
}, __("Get Items From"));
if (!this.frm.doc.is_return) {
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
if (value) {
this.frm.doc.items.forEach((item) => {
this.frm.fields_dict.items.grid.update_docfield_property(
"rate", "read_only", (item.purchase_receipt && item.pr_detail)
);
});
}
});
}
}
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);

View File

@@ -1660,10 +1660,6 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def update_item(obj, target, source_parent):

View File

@@ -286,7 +286,6 @@
"oldfieldname": "import_rate",
"oldfieldtype": "Currency",
"options": "currency",
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
"reqd": 1
},
{
@@ -894,7 +893,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-11-30 16:26:05.629780",
"modified": "2023-12-25 22:00:28.043555",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -2388,10 +2388,6 @@ def get_loyalty_programs(customer):
return lp_details
def on_doctype_update():
frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
@frappe.whitelist()
def create_invoice_discounting(source_name, target_doc=None):
invoice = frappe.get_doc("Sales Invoice", source_name)

View File

@@ -149,11 +149,16 @@ frappe.query_reports["Accounts Payable"] = {
"label": __("Revaluation Journals"),
"fieldtype": "Check",
},
{
"fieldname": "in_party_currency",
"label": __("In Party Currency"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
}
},
],

View File

@@ -40,6 +40,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
"range2": 60,
"range3": 90,
"range4": 120,
"in_party_currency": 1,
}
data = execute(filters)

View File

@@ -181,13 +181,17 @@ frappe.query_reports["Accounts Receivable"] = {
"label": __("Revaluation Journals"),
"fieldtype": "Check",
},
{
"fieldname": "in_party_currency",
"label": __("In Party Currency"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
}
],
"formatter": function(value, row, column, data, default_formatter) {

View File

@@ -28,8 +28,8 @@ from erpnext.accounts.utils import get_currency_precision
# 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters
# 7. For overpayment against an invoice with payment terms, there will be an additional row
# 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated
# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party
# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable"
# 9. Report amounts are in party currency if in_party_currency is selected, otherwise company currency
# 10. This report is based on Payment Ledger Entries
def execute(filters=None):
@@ -84,6 +84,9 @@ class ReceivablePayableReport(object):
self.total_row_map = {}
self.skip_total_row = 1
if self.filters.get("in_party_currency"):
self.skip_total_row = 1
def get_data(self):
self.get_ple_entries()
self.get_sales_invoices_or_customers_based_on_sales_person()
@@ -145,7 +148,7 @@ class ReceivablePayableReport(object):
if self.filters.get("group_by_party"):
self.init_subtotal_row(ple.party)
if self.filters.get("group_by_party"):
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
self.init_subtotal_row("Total")
def get_invoices(self, ple):
@@ -224,8 +227,7 @@ class ReceivablePayableReport(object):
if not row:
return
# amount in "Party Currency", if its supplied. If not, amount in company currency
if self.filters.get("party_type") and self.filters.get("party"):
if self.filters.get("in_party_currency") or self.filters.get("party_account"):
amount = ple.amount_in_account_currency
else:
amount = ple.amount
@@ -260,8 +262,10 @@ class ReceivablePayableReport(object):
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
for field in self.get_currency_fields():
total_row[field] += row.get(field, 0.0)
if total_row:
for field in self.get_currency_fields():
total_row[field] += row.get(field, 0.0)
total_row["currency"] = row.get("currency", "")
def append_subtotal_row(self, party):
sub_total_row = self.total_row_map.get(party)
@@ -322,7 +326,7 @@ class ReceivablePayableReport(object):
if self.filters.get("group_by_party"):
self.append_subtotal_row(self.previous_party)
if self.data:
self.data.append(self.total_row_map.get("Total"))
self.data.append(self.total_row_map.get("Total", {}))
def append_row(self, row):
self.allocate_future_payments(row)
@@ -453,7 +457,7 @@ class ReceivablePayableReport(object):
party_details = self.get_party_details(row.party) or {}
row.update(party_details)
if self.filters.get("party_type") and self.filters.get("party"):
if self.filters.get("in_party_currency") or self.filters.get("party_account"):
row.currency = row.account_currency
else:
row.currency = self.company_currency

View File

@@ -579,7 +579,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
filters.update({"party_account": self.debtors_usd})
report = execute(filters)[1]
self.assertEqual(len(report), 1)
expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency]
expected_data = [100.0, 100.0, self.debtors_usd, si2.currency]
row = report[0]
self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency]
@@ -616,6 +616,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
"range2": 60,
"range3": 90,
"range4": 120,
"in_party_currency": 1,
}
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)

View File

@@ -345,21 +345,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if filters.get("party"):
party = [filters.get("party")]
query = query.where(
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
| gle.party.isin(party)
jv_condition = gle.against.isin(party) | (
(gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))
)
else:
party = frappe.get_all(filters.get("party_type"), pluck="name")
query = query.where(
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
| (
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
| gle.party.isin(party)
jv_condition = gle.against.isin(party) | (
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
return query

View File

@@ -35,6 +35,8 @@
"purchase_receipt",
"purchase_invoice",
"available_for_use_date",
"total_asset_cost",
"additional_asset_cost",
"column_break_23",
"gross_purchase_amount",
"asset_quantity",
@@ -532,6 +534,22 @@
"label": "Capitalized In",
"options": "Asset Capitalization",
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus > 0",
"fieldname": "total_asset_cost",
"fieldtype": "Currency",
"label": "Total Asset Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus > 0",
"fieldname": "additional_asset_cost",
"fieldtype": "Currency",
"label": "Additional Asset Cost",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"idx": 72,
@@ -565,7 +583,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2023-11-20 21:05:45.216899",
"modified": "2023-12-21 16:46:20.732869",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -609,4 +627,4 @@
"states": [],
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -48,6 +48,7 @@ class Asset(AccountsController):
if self.get("schedules"):
self.validate_expected_value_after_useful_life()
self.total_asset_cost = self.gross_purchase_amount
self.status = self.get_status()
def on_submit(self):

View File

@@ -46,6 +46,10 @@ class AssetRepair(AccountsController):
self.increase_asset_value()
if self.capitalize_repair_cost:
self.asset_doc.total_asset_cost += self.repair_cost
self.asset_doc.additional_asset_cost += self.repair_cost
if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
@@ -68,6 +72,10 @@ class AssetRepair(AccountsController):
self.decrease_asset_value()
if self.capitalize_repair_cost:
self.asset_doc.total_asset_cost -= self.repair_cost
self.asset_doc.additional_asset_cost -= self.repair_cost
if self.get("stock_consumption"):
self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):

View File

@@ -123,8 +123,7 @@
"oldfieldname": "item_code",
"oldfieldtype": "Link",
"options": "Item",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"fieldname": "supplier_part_no",

View File

@@ -428,7 +428,9 @@ class SellingController(StockController):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
if not d.incoming_rate:
if not d.incoming_rate or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
):
d.incoming_rate = get_incoming_rate(
{
"item_code": d.item_code,

View File

@@ -433,8 +433,11 @@ class SubcontractingController(StockController):
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
self.available_materials[key]["batch_no"][batch_no] = 0
if abs(qty) > 0 and not new_rm_obj:
if new_rm_obj:
self.remove(rm_obj)
elif abs(qty) > 0:
self.__set_consumed_qty(rm_obj, qty)
else:
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
self.__set_serial_nos(item_row, rm_obj)
@@ -525,6 +528,10 @@ class SubcontractingController(StockController):
(row.item_code, row.get(self.subcontract_data.order_field))
] -= row.qty
def __reset_idx(self):
for idx, item in enumerate(self.get(self.raw_material_table)):
item.idx = idx + 1
def __prepare_supplied_items(self):
self.initialized_fields()
self.__get_subcontract_orders()
@@ -532,6 +539,7 @@ class SubcontractingController(StockController):
self.get_available_materials()
self.__remove_changed_rows()
self.__set_supplied_items()
self.__reset_idx()
def __validate_batch_no(self, row, key):
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(

View File

@@ -214,6 +214,7 @@
"options": "\nWork Order\nJob Card"
},
{
"default": "1",
"fieldname": "conversion_rate",
"fieldtype": "Float",
"label": "Conversion Rate",
@@ -606,7 +607,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2023-04-06 12:47:58.514795",
"modified": "2023-12-26 19:34:08.159312",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
@@ -645,4 +646,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -661,6 +661,9 @@ class BOM(WebsiteGenerator):
base_total_rm_cost = 0
for d in self.get("items"):
if not d.is_stock_item and self.rm_cost_as_per == "Valuation Rate":
continue
old_rate = d.rate
d.rate = self.get_rm_rate(
{

View File

@@ -698,6 +698,35 @@ class TestBOM(FrappeTestCase):
bom.update_cost()
self.assertFalse(bom.flags.cost_updated)
def test_bom_with_service_item_cost(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 1000.0}).name
service_item = make_item(properties={"is_stock_item": 0}).name
fg_item = make_item(properties={"is_stock_item": 1}).name
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
bom = make_bom(item=fg_item, raw_materials=[rm_item, service_item], do_not_save=True)
bom.rm_cost_as_per = "Valuation Rate"
for row in bom.items:
if row.item_code == service_item:
row.rate = 566.00
else:
row.rate = 800.00
bom.save()
for row in bom.items:
if row.item_code == service_item:
self.assertEqual(row.is_stock_item, 0)
self.assertEqual(row.rate, 566.00)
else:
self.assertEqual(row.is_stock_item, 1)
def test_do_not_include_manufacturing_and_fixed_items(self):
from erpnext.manufacturing.doctype.bom.bom import item_query

View File

@@ -14,6 +14,7 @@
"bom_no",
"source_warehouse",
"allow_alternative_item",
"is_stock_item",
"section_break_5",
"description",
"col_break1",
@@ -185,7 +186,7 @@
"in_list_view": 1,
"label": "Rate",
"options": "currency",
"read_only": 1,
"read_only_depends_on": "eval:doc.is_stock_item == 1",
"reqd": 1
},
{
@@ -284,13 +285,21 @@
"fieldname": "do_not_explode",
"fieldtype": "Check",
"label": "Do Not Explode"
},
{
"default": "0",
"fetch_from": "item_code.is_stock_item",
"fieldname": "is_stock_item",
"fieldtype": "Check",
"label": "Is Stock Item",
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-14 18:35:51.378513",
"modified": "2023-12-20 16:21:55.477883",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",

View File

@@ -305,6 +305,8 @@ frappe.ui.form.on('Production Plan', {
frappe.throw(__("Select the Warehouse"));
}
frm.set_value("consider_minimum_order_qty", 0);
if (frm.doc.ignore_existing_ordered_qty) {
frm.events.get_items_for_material_requests(frm);
} else {

View File

@@ -48,6 +48,7 @@
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
"consider_minimum_order_qty",
"include_safety_stock",
"ignore_existing_ordered_qty",
"column_break_25",
@@ -423,13 +424,19 @@
"fieldtype": "Link",
"label": "Sub Assembly Warehouse",
"options": "Warehouse"
},
{
"default": "0",
"fieldname": "consider_minimum_order_qty",
"fieldtype": "Check",
"label": "Consider Minimum Order Qty"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-11-03 14:08:11.928027",
"modified": "2023-12-26 16:31:13.740777",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@@ -1135,7 +1135,14 @@ def get_subitems(
def get_material_request_items(
row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
doc,
row,
sales_order,
company,
ignore_existing_ordered_qty,
include_safety_stock,
warehouse,
bin_dict,
):
total_qty = row["qty"]
@@ -1144,8 +1151,14 @@ def get_material_request_items(
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty", 0)
if required_qty > 0 and required_qty < row["min_order_qty"]:
if (
doc.get("consider_minimum_order_qty")
and required_qty > 0
and required_qty < row["min_order_qty"]
):
required_qty = row["min_order_qty"]
item_group_defaults = get_item_group_defaults(row.item_code, company)
if not row["purchase_uom"]:
@@ -1483,6 +1496,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
if details.qty > 0:
items = get_material_request_items(
doc,
details,
sales_order,
company,

View File

@@ -1488,6 +1488,29 @@ class TestProductionPlan(FrappeTestCase):
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertAlmostEqual(after_qty, before_qty)
def test_min_order_qty_in_pp(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_or_make_bin
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name
rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
pln.for_warehouse = rm_warehouse
mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items:
self.assertEqual(d.get("quantity"), 10.0)
pln.consider_minimum_order_qty = 1
mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items:
self.assertEqual(d.get("quantity"), 1000.0)
def create_production_plan(**args):
"""

View File

@@ -352,6 +352,8 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
erpnext.patches.v14_0.update_total_asset_cost_field
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item

View File

@@ -0,0 +1,19 @@
import frappe
def execute():
if not frappe.db.exists("BOM", {"docstatus": 1}):
return
# Added is_stock_item to handle Read Only based on condition for the rate field
frappe.db.sql(
"""
UPDATE
`tabBOM Item` boi,
`tabItem` i
SET
boi.is_stock_item = i.is_stock_item
WHERE
boi.item_code = i.name
"""
)

View File

@@ -0,0 +1,17 @@
import frappe
def execute():
asset = frappe.qb.DocType("Asset")
frappe.qb.update(asset).set(asset.total_asset_cost, asset.gross_purchase_amount).run()
asset_repair_list = frappe.db.get_all(
"Asset Repair",
filters={"docstatus": 1, "repair_status": "Completed", "capitalize_repair_cost": 1},
fields=["asset", "repair_cost"],
)
for asset_repair in asset_repair_list:
frappe.qb.update(asset).set(
asset.total_asset_cost, asset.total_asset_cost + asset_repair.repair_cost
).where(asset.name == asset_repair.asset).run()

View File

@@ -1,431 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.selling");
erpnext.sales_common = {
setup_selling_controller:function() {
erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
setup() {
super.setup();
this.toggle_enable_for_stock_uom("allow_to_edit_stock_uom_qty_for_sales");
this.frm.email_field = "contact_email";
}
onload() {
super.onload();
this.setup_queries();
this.frm.set_query('shipping_rule', function() {
return {
filters: {
"shipping_rule_type": "Selling"
}
};
});
}
setup_queries() {
var me = this;
$.each([["customer", "customer"],
["lead", "lead"]],
function(i, opts) {
if(me.frm.fields_dict[opts[0]])
me.frm.set_query(opts[0], erpnext.queries[opts[1]]);
});
me.frm.set_query('contact_person', erpnext.queries.contact_query);
me.frm.set_query('customer_address', erpnext.queries.address_query);
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
if(this.frm.fields_dict.selling_price_list) {
this.frm.set_query("selling_price_list", function() {
return { filters: { selling: 1 } };
});
}
if(this.frm.fields_dict.tc_name) {
this.frm.set_query("tc_name", function() {
return { filters: { selling: 1 } };
});
}
if(!this.frm.fields_dict["items"]) {
return;
}
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
this.frm.set_query("item_code", "items", function() {
return {
query: "erpnext.controllers.queries.item_query",
filters: {'is_sales_item': 1, 'customer': me.frm.doc.customer, 'has_variants': 0}
}
});
}
if(this.frm.fields_dict["packed_items"] &&
this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) {
this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) {
return me.set_query_for_batch(doc, cdt, cdn)
});
}
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
return me.set_query_for_item_tax_template(doc, cdt, cdn)
});
}
}
refresh() {
super.refresh();
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
this.frm.toggle_display("customer_name",
(this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer));
this.toggle_editable_price_list_rate();
}
customer() {
var me = this;
erpnext.utils.get_party_details(this.frm, null, null, function() {
me.apply_price_list();
});
}
customer_address() {
erpnext.utils.get_address_display(this.frm, "customer_address");
erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name");
}
shipping_address_name() {
erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address");
erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
}
dispatch_address_name() {
erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address");
}
sales_partner() {
this.apply_pricing_rule();
}
campaign() {
this.apply_pricing_rule();
}
selling_price_list() {
this.apply_price_list();
this.set_dynamic_labels();
}
discount_percentage(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
item.discount_amount = 0.0;
this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage');
}
discount_amount(doc, cdt, cdn) {
if(doc.name === cdn) {
return;
}
var item = frappe.get_doc(cdt, cdn);
item.discount_percentage = 0.0;
this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount');
}
commission_rate() {
this.calculate_commission();
}
total_commission() {
frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
const { amount_eligible_for_commission } = this.frm.doc;
if(!amount_eligible_for_commission) return;
this.frm.set_value(
"commission_rate", flt(
this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
)
);
}
allocated_percentage(doc, cdt, cdn) {
var sales_person = frappe.get_doc(cdt, cdn);
if(sales_person.allocated_percentage) {
sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
precision("allocated_percentage", sales_person));
sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission *
sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person));
refresh_field(["allocated_amount"], sales_person);
this.calculate_incentive(sales_person);
refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
sales_person.parentfield);
}
}
sales_person(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.calculate_incentive(row);
refresh_field("incentives",row.name,row.parentfield);
}
toggle_editable_price_list_rate() {
var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
if(df && editable_price_list_rate) {
const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item");
if (!this.frm.fields_dict[parent_field]) return;
this.frm.fields_dict[parent_field].grid.update_docfield_property(
'price_list_rate', 'read_only', 0
);
}
}
calculate_commission() {
if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
if(this.frm.doc.commission_rate > 100) {
this.frm.set_value("commission_rate", 100);
frappe.throw(`${__(frappe.meta.get_label(
this.frm.doc.doctype, "commission_rate", this.frm.doc.name
))} ${__("cannot be greater than 100")}`);
}
this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce(
(sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0
)
this.frm.doc.total_commission = flt(
this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0,
precision("total_commission")
);
refresh_field(["amount_eligible_for_commission", "total_commission"]);
}
calculate_contribution() {
var me = this;
$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
frappe.model.round_floats_in(sales_person);
if (!sales_person.allocated_percentage) return;
sales_person.allocated_amount = flt(
me.frm.doc.amount_eligible_for_commission
* sales_person.allocated_percentage
/ 100.0,
precision("allocated_amount", sales_person)
);
});
}
calculate_incentive(row) {
if(row.allocated_amount)
{
row.incentives = flt(
row.allocated_amount * row.commission_rate / 100.0,
precision("incentives", row));
}
}
set_dynamic_labels() {
super.set_dynamic_labels();
this.set_product_bundle_help(this.frm.doc);
}
set_product_bundle_help(doc) {
if(!this.frm.fields_dict.packing_list) return;
if ((doc.packed_items || []).length) {
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(true);
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
var help_msg = "<div class='alert alert-warning'>" +
__("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+
"</div>";
frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg;
}
} else {
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(false);
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = '';
}
}
refresh_field('product_bundle_help');
}
company_address() {
var me = this;
if(this.frm.doc.company_address) {
frappe.call({
method: "frappe.contacts.doctype.address.address.get_address_display",
args: {"address_dict": this.frm.doc.company_address },
callback: function(r) {
if(r.message) {
me.frm.set_value("company_address_display", r.message)
}
}
})
} else {
this.frm.set_value("company_address_display", "");
}
}
conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate);
}
qty(doc, cdt, cdn) {
super.qty(doc, cdt, cdn);
}
pick_serial_and_batch(doc, cdt, cdn) {
let item = locals[cdt][cdn];
let me = this;
let path = "assets/erpnext/js/utils/serial_no_batch_selector.js";
frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
.then((r) => {
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
item.has_serial_no = r.message.has_serial_no;
item.has_batch_no = r.message.has_batch_no;
item.type_of_transaction = item.qty > 0 ? "Outward":"Inward";
item.title = item.has_serial_no ?
__("Select Serial No") : __("Select Batch No");
if (item.has_serial_no && item.has_batch_no) {
item.title = __("Select Serial and Batch");
}
frappe.require(path, function() {
new erpnext.SerialBatchPackageSelector(
me.frm, item, (r) => {
if (r) {
frappe.model.set_value(item.doctype, item.name, {
"serial_and_batch_bundle": r.name,
"qty": Math.abs(r.total_qty)
});
}
}
);
});
}
});
}
update_auto_repeat_reference(doc) {
if (doc.auto_repeat) {
frappe.call({
method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
args:{
docname: doc.auto_repeat,
reference:doc.name
},
callback: function(r){
if (r.message=="success") {
frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'});
} else {
frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'});
}
}
})
}
}
project() {
let me = this;
if(in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) {
if(this.frm.doc.project) {
frappe.call({
method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
args: {project: this.frm.doc.project},
callback: function(r, rt) {
if(!r.exc) {
$.each(me.frm.doc["items"] || [], function(i, row) {
if(r.message) {
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
}
})
}
}
})
}
}
}
coupon_code() {
this.frm.set_value("discount_amount", 0);
this.frm.set_value("additional_discount_percentage", 0);
}
};
}
}
erpnext.pre_sales = {
set_as_lost: function(doctype) {
frappe.ui.form.on(doctype, {
set_as_lost_dialog: function(frm) {
var dialog = new frappe.ui.Dialog({
title: __("Set as Lost"),
fields: [
{
"fieldtype": "Table MultiSelect",
"label": __("Lost Reasons"),
"fieldname": "lost_reason",
"options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
"reqd": 1
},
{
"fieldtype": "Table MultiSelect",
"label": __("Competitors"),
"fieldname": "competitors",
"options": "Competitor Detail"
},
{
"fieldtype": "Small Text",
"label": __("Detailed Reason"),
"fieldname": "detailed_reason"
},
],
primary_action: function() {
let values = dialog.get_values();
frm.call({
doc: frm.doc,
method: 'declare_enquiry_lost',
args: {
'lost_reasons_list': values.lost_reason,
'competitors': values.competitors ? values.competitors : [],
'detailed_reason': values.detailed_reason
},
callback: function(r) {
dialog.hide();
frm.reload_doc();
},
});
},
primary_action_label: __('Declare Lost')
});
dialog.show();
}
});
}
}

View File

@@ -702,6 +702,7 @@ def make_contact(args, is_primary_contact=1):
else:
values.update(
{
"first_name": args.get("customer_name"),
"company_name": args.get("customer_name"),
}
)

View File

@@ -3,11 +3,11 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from erpnext import get_default_company
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_price_list_rate_for
def execute(filters=None):
@@ -50,6 +50,42 @@ def get_columns(filters=None):
]
def fetch_item_prices(
customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
):
price_list_map = frappe._dict()
ip = qb.DocType("Item Price")
and_conditions = []
or_conditions = []
if items:
and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
and_conditions.append(ip.selling == True)
or_conditions.append(ip.customer == None)
or_conditions.append(ip.price_list == None)
if customer:
or_conditions.append(ip.customer == customer)
if price_list:
or_conditions.append(ip.price_list == price_list)
if selling_price_list:
or_conditions.append(ip.price_list == selling_price_list)
res = (
qb.from_(ip)
.select(ip.item_code, ip.price_list, ip.price_list_rate)
.where(Criterion.all(and_conditions))
.where(Criterion.any(or_conditions))
.run(as_dict=True)
)
for x in res:
price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
return price_list_map
def get_data(filters=None):
data = []
customer_details = get_customer_details(filters)
@@ -59,9 +95,17 @@ def get_data(filters=None):
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
)
item_stock_map = {item.item_code: item.available for item in item_stock_map}
price_list_map = fetch_item_prices(
customer_details.customer,
customer_details.price_list,
customer_details.selling_price_list,
items,
)
for item in items:
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
price_list_rate = price_list_map.get(
(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
)
available_stock = item_stock_map.get(item.item_code)
data.append(

View File

@@ -200,6 +200,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
item.serial_no = null;
}
if (doc.docstatus === 0 && doc.is_return && !doc.return_against) {
item.incoming_rate = 0.0;
}
var has_batch_no;
frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_batch_no', (r) => {
has_batch_no = r && r.has_batch_no;
@@ -428,6 +432,11 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
})
}
}
coupon_code() {
this.frm.set_value("discount_amount", 0);
this.frm.set_value("additional_discount_percentage", 0);
}
};
frappe.ui.form.on(cur_frm.doctype,"project", function(frm) {

View File

@@ -51,8 +51,7 @@
"oldfieldtype": "Link",
"options": "Item",
"read_only": 1,
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"default": "0.00",

View File

@@ -1,15 +1,27 @@
import click
import frappe
UNUSED_INDEXES = [
("Delivery Note", ["customer", "is_return", "return_against"]),
("Sales Invoice", ["customer", "is_return", "return_against"]),
("Purchase Invoice", ["supplier", "is_return", "return_against"]),
("Purchase Receipt", ["supplier", "is_return", "return_against"]),
]
def execute():
"""Drop unused return_against index"""
for doctype, index_fields in UNUSED_INDEXES:
table = f"tab{doctype}"
index_name = frappe.db.get_index_name(index_fields)
drop_index_if_exists(table, index_name)
def drop_index_if_exists(table: str, index: str):
if not frappe.db.has_index(table, index):
return
try:
frappe.db.sql_ddl(
"ALTER TABLE `tabDelivery Note` DROP INDEX `customer_is_return_return_against_index`"
)
frappe.db.sql_ddl(
"ALTER TABLE `tabPurchase Receipt` DROP INDEX `supplier_is_return_return_against_index`"
)
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
click.echo(f"✓ dropped {index} index from {table}")
except Exception:
frappe.log_error("Failed to drop unused index")
frappe.log_error("Failed to drop index")

View File

@@ -1382,6 +1382,59 @@ class TestDeliveryNote(FrappeTestCase):
self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0)
def test_sales_return_valuation_for_moving_average_case2(self):
# Make DN return
# Make Bakcdated Purchase Receipt and check DN return valuation rate
# The rate should be recalculate based on the backdated purchase receipt
frappe.flags.print_debug_messages = False
item_code = make_item(
"_Test Item Sales Return with MA Case2",
{"is_stock_item": 1, "valuation_method": "Moving Average", "stock_uom": "Nos"},
).name
make_stock_entry(
item_code=item_code,
target="_Test Warehouse - _TC",
qty=5,
basic_rate=100.0,
posting_date=add_days(nowdate(), -5),
)
dn = create_delivery_note(
item_code=item_code,
warehouse="_Test Warehouse - _TC",
qty=5,
rate=500,
posting_date=add_days(nowdate(), -4),
)
returned_dn = create_delivery_note(
is_return=1,
item_code=item_code,
return_against=dn.name,
qty=-5,
rate=500,
company=dn.company,
warehouse="_Test Warehouse - _TC",
expense_account="Cost of Goods Sold - _TC",
cost_center="Main - _TC",
posting_date=add_days(nowdate(), -1),
)
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 100.0)
# Make backdated purchase receipt
make_stock_entry(
item_code=item_code,
target="_Test Warehouse - _TC",
qty=5,
basic_rate=200.0,
posting_date=add_days(nowdate(), -3),
)
returned_dn.reload()
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -78,6 +78,20 @@ frappe.ui.form.on("Purchase Receipt", {
}, __('Create'));
}
if (frm.doc.docstatus === 0) {
if (!frm.doc.is_return) {
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
if (value) {
frm.doc.items.forEach((item) => {
frm.fields_dict.items.grid.update_docfield_property(
"rate", "read_only", (item.purchase_order && item.purchase_order_item)
);
});
}
});
}
}
frm.events.add_custom_buttons(frm);
},

View File

@@ -352,7 +352,6 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_width": "100px",
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
"width": "100px"
},
{
@@ -1055,7 +1054,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-11-30 16:12:02.364608",
"modified": "2023-12-25 22:32:09.801965",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -517,7 +517,12 @@ frappe.ui.form.on('Stock Entry', {
},
callback: function(r) {
if (!r.exc) {
["actual_qty", "basic_rate"].forEach((field) => {
let fields = ["actual_qty", "basic_rate"];
if (frm.doc.purpose == "Material Receipt") {
fields = ["actual_qty"];
}
fields.forEach((field) => {
frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
});
frm.events.calculate_basic_amount(frm, child);

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.utils import cint, flt, getdate
from frappe.utils import cint, flt, get_table_name, getdate
from pypika import functions as fn
from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
@@ -12,11 +12,22 @@ from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
SLE_COUNT_LIMIT = 10_000
def _estimate_table_row_count(doctype: str):
table = get_table_name(doctype)
return cint(
frappe.db.sql(
f"""select table_rows
from information_schema.tables
where table_name = '{table}' ;"""
)[0][0]
)
def execute(filters=None):
if not filters:
filters = {}
sle_count = frappe.db.count("Stock Ledger Entry")
sle_count = _estimate_table_row_count("Stock Ledger Entry")
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))

View File

@@ -16,6 +16,7 @@ from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
from erpnext.stock.utils import (
get_incoming_outgoing_rate_for_cancel,
get_incoming_rate,
get_or_make_bin,
get_valuation_method,
)
@@ -701,7 +702,23 @@ class update_entries_after(object):
)
if self.valuation_method == "Moving Average":
rate = flt(self.data[self.args.warehouse].previous_sle.valuation_rate)
rate = get_incoming_rate(
{
"item_code": sle.item_code,
"warehouse": sle.warehouse,
"posting_date": sle.posting_date,
"posting_time": sle.posting_time,
"qty": sle.actual_qty,
"serial_no": sle.get("serial_no"),
"batch_no": sle.get("batch_no"),
"company": sle.company,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no,
"allow_zero_valuation": self.allow_zero_rate,
"sle": sle.name,
}
)
else:
rate = get_rate_for_return(
sle.voucher_type,

View File

@@ -0,0 +1,24 @@
import frappe
from frappe.tests.utils import FrappeTestCase
INDEXED_FIELDS = {
"Bin": ["item_code"],
"GL Entry": ["voucher_type", "against_voucher_type"],
"Purchase Order Item": ["item_code"],
"Stock Ledger Entry": ["warehouse"],
}
class TestPerformance(FrappeTestCase):
def test_ensure_indexes(self):
# These fields are not explicitly indexed BUT they are prefix in some
# other composite index. If those are removed this test should be
# updated accordingly.
for doctype, fields in INDEXED_FIELDS.items():
for field in fields:
self.assertTrue(
frappe.db.sql(
f"""SHOW INDEX FROM `tab{doctype}`
WHERE Column_name = "{field}" AND Seq_in_index = 1"""
)
)

View File

@@ -9104,3 +9104,7 @@ Select an item from each set to be used in the Sales Order.,"Wählen Sie aus den
Is Alternative,Ist Alternative,
Alternative Items,Alternativpositionen,
Component Type,Komponententyp,
Lost Quotations,Verlorene Angebote,
Lost Quotations %,Verlorene Angebote %,
Lost Value,Verlorener Wert,
Lost Value %,Verlorener Wert %,
Can't render this file because it is too large.

View File

@@ -98,6 +98,7 @@ class TransactionBase(StatusUpdater):
"Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
)
stop_actions = []
for ref_dt, ref_dn_field, ref_link_field in ref_details:
reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)]
reference_details = self.get_reference_details(reference_names, ref_dt + " Item")
@@ -108,7 +109,7 @@ class TransactionBase(StatusUpdater):
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
if action == "Stop":
if role_allowed_to_override not in frappe.get_roles():
frappe.throw(
stop_actions.append(
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
)
@@ -121,6 +122,8 @@ class TransactionBase(StatusUpdater):
title=_("Warning"),
indicator="orange",
)
if stop_actions:
frappe.throw(stop_actions, as_list=True)
def get_reference_details(self, reference_names, reference_doctype):
return frappe._dict(