Compare commits

..

45 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
25 changed files with 265 additions and 137 deletions

View File

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

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

@@ -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

@@ -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

@@ -275,6 +275,9 @@ class GLEntry(Document):
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

@@ -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

@@ -2494,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

@@ -760,6 +760,9 @@ def validate_party_frozen_disabled(party_type, party_name):
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"]):

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

@@ -2353,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

@@ -103,6 +103,16 @@ 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

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

@@ -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

@@ -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

@@ -2401,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

@@ -813,7 +813,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
validate() {
this.apply_pricing_rule()
this.calculate_taxes_and_totals(false);
}

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

@@ -928,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

@@ -328,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();
@@ -346,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

@@ -1183,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

@@ -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):

View File

@@ -1093,6 +1093,7 @@ class StockReconciliation(StockController):
posting_date=doc.posting_date,
posting_time=doc.posting_time,
ignore_voucher_nos=[doc.voucher_no],
for_stock_levels=True,
)
or 0
) * -1

View File

@@ -54,7 +54,7 @@
"label": "Status",
"max_length": 0,
"max_value": 0,
"options": "Open\nReplied\nHold\nClosed",
"options": "Open\nReplied\nOn Hold\nClosed",
"read_only": 1,
"reqd": 0,
"show_in_filter": 1