Merge pull request #39204 from shariquerik/add-expected-start-date-in-sort
fix: add expected_start_date in sort by
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index f240aa6..0b29769 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -77,7 +77,7 @@
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
- const dr_or_cr = balance > 0 ? "Dr": "Cr";
+ const dr_or_cr = balance > 0 ? __("Dr"): __("Cr");
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) {
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 9e67c4c..9f56455 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -74,7 +74,7 @@
# after all accounts are already inserted.
frappe.local.flags.ignore_update_nsm = True
_import_accounts(chart, None, None, root_account=True)
- rebuild_tree("Account", "parent_account")
+ rebuild_tree("Account")
frappe.local.flags.ignore_update_nsm = False
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 629ed1c..57f7c0e 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -366,15 +366,17 @@
and len(get_reconciled_bank_transactions(doctype, docname)) < 2
):
return
- frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
- elif doctype == "Sales Invoice":
- frappe.db.set_value(
- "Sales Invoice Payment",
- dict(parenttype=doctype, parent=docname),
- "clearance_date",
- clearance_date,
- )
+ if doctype == "Sales Invoice":
+ frappe.db.set_value(
+ "Sales Invoice Payment",
+ dict(parenttype=doctype, parent=docname),
+ "clearance_date",
+ clearance_date,
+ )
+ return
+
+ frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 2611240..81ffee3 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -747,6 +747,10 @@
args["get_orders_to_be_billed"] = true;
}
+ if (frm.doc.book_advance_payments_in_separate_party_account) {
+ args["book_advance_payments_in_separate_party_account"] = true;
+ }
+
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 11c7c17..052eff3 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -256,6 +256,7 @@
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
"vouchers": vouchers,
+ "book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
},
validate=True,
)
@@ -1628,11 +1629,16 @@
outstanding_invoices = []
negative_outstanding_invoices = []
+ if args.get("book_advance_payments_in_separate_party_account"):
+ party_account = get_party_account(args.get("party_type"), args.get("party"), args.get("company"))
+ else:
+ party_account = args.get("party_account")
+
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
- get_party_account(args.get("party_type"), args.get("party"), args.get("company")),
+ party_account,
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 008614e..fc9034b 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -114,14 +114,12 @@
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
)
party = party_details[party_type.lower()]
-
- if not ignore_permissions and not (
- frappe.has_permission(party_type, "read", party)
- or frappe.has_permission(party_type, "select", party)
- ):
- frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
-
party = frappe.get_doc(party_type, party)
+
+ if not ignore_permissions:
+ ptype = "select" if frappe.only_has_select_perm(party_type) else "read"
+ frappe.has_permission(party_type, ptype, party, throw=True)
+
currency = party.get("default_currency") or currency or get_company_currency(company)
party_address, shipping_address = set_address_details(
@@ -637,9 +635,7 @@
return due_date
-def validate_due_date(
- posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
-):
+def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
else:
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 9f96449..0912c72 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -251,6 +251,7 @@
)
.where(
(je.voucher_type == "Journal Entry")
+ & (je.docstatus == 1)
& (journal_account.party == filters.get(args.party))
& (journal_account.account.isin(args.party_account))
)
@@ -281,7 +282,9 @@
pe.cost_center,
)
.where(
- (pe.party == filters.get(args.party)) & (pe[args.account_fieldname].isin(args.party_account))
+ (pe.docstatus == 1)
+ & (pe.party == filters.get(args.party))
+ & (pe[args.account_fieldname].isin(args.party_account))
)
.orderby(pe.posting_date, pe.name, order=Order.desc)
)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index ac712d4..d0c9350 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -202,9 +202,9 @@
"fieldname": "purchase_date",
"fieldtype": "Date",
"label": "Purchase Date",
+ "mandatory_depends_on": "eval:!doc.is_existing_asset",
"read_only": 1,
- "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
- "reqd": 1
+ "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
},
{
"fieldname": "disposal_date",
@@ -227,15 +227,15 @@
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
+ "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency",
- "read_only_depends_on": "eval:!doc.is_existing_asset",
- "reqd": 1
+ "read_only_depends_on": "eval:!doc.is_existing_asset"
},
{
"fieldname": "available_for_use_date",
"fieldtype": "Date",
"label": "Available-for-use Date",
- "reqd": 1
+ "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)"
},
{
"default": "0",
@@ -590,7 +590,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2023-12-21 16:46:20.732869",
+ "modified": "2024-01-05 17:36:53.131512",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index dd34189..5f44898 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -57,7 +57,7 @@
asset_owner: DF.Literal["", "Company", "Supplier", "Customer"]
asset_owner_company: DF.Link | None
asset_quantity: DF.Int
- available_for_use_date: DF.Date
+ available_for_use_date: DF.Date | None
booked_fixed_asset: DF.Check
calculate_depreciation: DF.Check
capitalized_in: DF.Link | None
@@ -92,7 +92,7 @@
number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency
policy_number: DF.Data | None
- purchase_date: DF.Date
+ purchase_date: DF.Date | None
purchase_invoice: DF.Link | None
purchase_receipt: DF.Link | None
purchase_receipt_amount: DF.Currency
@@ -316,7 +316,12 @@
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category):
- if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice:
+ if (
+ not self.is_existing_asset
+ and not self.is_composite_asset
+ and not self.purchase_receipt
+ and not self.purchase_invoice
+ ):
frappe.throw(
_("Please create purchase receipt or purchase invoice for the item {0}").format(
self.item_code
@@ -329,7 +334,7 @@
and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
):
frappe.throw(
- _("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice)
+ _("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice)
)
if not self.calculate_depreciation:
diff --git a/erpnext/assets/doctype/asset_activity/asset_activity.py b/erpnext/assets/doctype/asset_activity/asset_activity.py
index a64cb1a..7177223 100644
--- a/erpnext/assets/doctype/asset_activity/asset_activity.py
+++ b/erpnext/assets/doctype/asset_activity/asset_activity.py
@@ -3,6 +3,7 @@
import frappe
from frappe.model.document import Document
+from frappe.utils import now_datetime
class AssetActivity(Document):
@@ -30,5 +31,6 @@
"asset": asset,
"subject": subject,
"user": frappe.session.user,
+ "date": now_datetime(),
}
).insert(ignore_permissions=True, ignore_links=True)
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index 034ec55..d401b81 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -86,12 +86,12 @@
if selected_key_type not in expected_key_types:
frappe.throw(
_(
- "Row #{}: {} of {} should be {}. Please modify the account or select a different account."
+ "Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account."
).format(
d.idx,
frappe.unscrub(key_to_match),
frappe.bold(selected_account),
- frappe.bold(expected_key_types),
+ frappe.bold(" or ".join(expected_key_types)),
),
title=_("Invalid Account"),
)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2efb46e..b830e7d 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -452,6 +452,7 @@
self.update_requested_qty()
self.update_ordered_qty()
self.update_reserved_qty_for_subcontract()
+ self.update_subcontracting_order_status()
self.notify_update()
clear_doctype_notifications(self)
@@ -627,6 +628,17 @@
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
make_subcontracting_order(self.name, save=True, notify=True)
+ def update_subcontracting_order_status(self):
+ from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
+ update_subcontracting_order_status as update_sco_status,
+ )
+
+ if self.is_subcontracted and not self.is_old_subcontracting_flow:
+ sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
+
+ if sco:
+ update_sco_status(sco, "Closed" if self.status == "Closed" else None)
+
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item"""
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index de7a7f9..82b5416 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -129,6 +129,17 @@
if self.doctype in relevant_docs:
self.set_payment_schedule()
+ def remove_bundle_for_non_stock_invoices(self):
+ has_sabb = False
+ if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
+ for item in self.get("items"):
+ if item.serial_and_batch_bundle:
+ item.serial_and_batch_bundle = None
+ has_sabb = True
+
+ if has_sabb:
+ self.remove_serial_and_batch_bundle()
+
def ensure_supplier_is_not_blocked(self):
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
@@ -156,6 +167,9 @@
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
+ if self.get("_action") == "submit":
+ self.remove_bundle_for_non_stock_invoices()
+
self.ensure_supplier_is_not_blocked()
self.validate_date_with_fiscal_year()
@@ -561,18 +575,12 @@
validate_due_date(
self.posting_date,
self.due_date,
- "Customer",
- self.customer,
- self.company,
self.payment_terms_template,
)
elif self.doctype == "Purchase Invoice":
validate_due_date(
self.bill_date or self.posting_date,
self.due_date,
- "Supplier",
- self.supplier,
- self.company,
self.bill_date,
self.payment_terms_template,
)
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 919e459..22b0d08 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -432,6 +432,9 @@
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
+ if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
+ continue
+
if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 0f80d5e..6efb893 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -489,6 +489,7 @@
"Payment Entry",
"Journal Entry",
"Purchase Invoice",
+ "Sales Invoice",
]
accounting_dimension_doctypes = [
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 6100756..ceb4406 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -117,7 +117,7 @@
self.update_amc_date(serial_nos, d.end_date)
no_email_sp = []
- if d.sales_person not in email_map:
+ if d.sales_person and d.sales_person not in email_map:
sp = frappe.get_doc("Sales Person", d.sales_person)
try:
email_map[d.sales_person] = sp.get_email_id()
@@ -131,12 +131,11 @@
).format(self.owner, "<br>" + "<br>".join(no_email_sp))
)
- scheduled_date = frappe.db.sql(
- """select scheduled_date from
- `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
- parent=%s""",
- (d.sales_person, d.item_code, self.name),
- as_dict=1,
+ scheduled_date = frappe.db.get_all(
+ "Maintenance Schedule Detail",
+ {"parent": self.name, "item_code": d.item_code},
+ ["scheduled_date"],
+ as_list=False,
)
for key in scheduled_date:
@@ -232,8 +231,6 @@
throw(_("Please select Start Date and End Date for Item {0}").format(d.item_code))
elif not d.no_of_visits:
throw(_("Please mention no of visits required"))
- elif not d.sales_person:
- throw(_("Please select a Sales Person for item: {0}").format(d.item_name))
if getdate(d.start_date) >= getdate(d.end_date):
throw(_("Start date should be less than end date for Item {0}").format(d.item_code))
@@ -452,20 +449,28 @@
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
from frappe.model.mapper import get_mapped_doc
+ def condition(doc):
+ if s_id:
+ return doc.name == s_id
+ elif item_name:
+ return doc.item_name == item_name
+
+ return True
+
def update_status_and_detail(source, target, parent):
target.maintenance_type = "Scheduled"
- target.maintenance_schedule_detail = s_id
def update_serial(source, target, parent):
- if source.serial_and_batch_bundle:
- serial_nos = frappe.get_doc(
- "Serial and Batch Bundle", source.serial_and_batch_bundle
- ).get_serial_nos()
+ if source.item_reference:
+ if sbb := frappe.db.get_value(
+ "Maintenance Schedule Item", source.item_reference, "serial_and_batch_bundle"
+ ):
+ serial_nos = frappe.get_doc("Serial and Batch Bundle", sbb).get_serial_nos()
- if len(serial_nos) == 1:
- target.serial_no = serial_nos[0]
- else:
- target.serial_no = ""
+ if len(serial_nos) == 1:
+ target.serial_no = serial_nos[0]
+ else:
+ target.serial_no = ""
doclist = get_mapped_doc(
"Maintenance Schedule",
@@ -477,10 +482,13 @@
"validation": {"docstatus": ["=", 1]},
"postprocess": update_status_and_detail,
},
- "Maintenance Schedule Item": {
+ "Maintenance Schedule Detail": {
"doctype": "Maintenance Visit Purpose",
- "condition": lambda doc: doc.item_name == item_name if item_name else True,
- "field_map": {"sales_person": "service_person"},
+ "condition": condition,
+ "field_map": {
+ "sales_person": "service_person",
+ "name": "maintenance_schedule_detail",
+ },
"postprocess": update_serial,
},
},
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index e7df484..d2511b8 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -56,20 +56,39 @@
frappe.throw(_("Add Items in the Purpose Table"), title=_("Purposes Required"))
def validate_maintenance_date(self):
- if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
- item_ref = frappe.db.get_value(
- "Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference"
- )
- if item_ref:
- start_date, end_date = frappe.db.get_value(
- "Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
+ if self.maintenance_type == "Scheduled":
+ if self.maintenance_schedule_detail:
+ item_ref = frappe.db.get_value(
+ "Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference"
)
- if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
- self.mntc_date
- ) > get_datetime(end_date):
- frappe.throw(
- _("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date))
+ if item_ref:
+ start_date, end_date = frappe.db.get_value(
+ "Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
)
+ if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
+ self.mntc_date
+ ) > get_datetime(end_date):
+ frappe.throw(
+ _("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date))
+ )
+ else:
+ for purpose in self.purposes:
+ if purpose.maintenance_schedule_detail:
+ item_ref = frappe.db.get_value(
+ "Maintenance Schedule Detail", purpose.maintenance_schedule_detail, "item_reference"
+ )
+ if item_ref:
+ start_date, end_date = frappe.db.get_value(
+ "Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
+ )
+ if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
+ self.mntc_date
+ ) > get_datetime(end_date):
+ frappe.throw(
+ _("Date must be between {0} and {1}").format(
+ format_date(start_date), format_date(end_date)
+ )
+ )
def validate(self):
self.validate_serial_no()
@@ -82,6 +101,7 @@
if not cancel:
status = self.completion_status
actual_date = self.mntc_date
+
if self.maintenance_schedule_detail:
frappe.db.set_value(
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "completion_status", status
@@ -89,6 +109,21 @@
frappe.db.set_value(
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "actual_date", actual_date
)
+ else:
+ for purpose in self.purposes:
+ if purpose.maintenance_schedule_detail:
+ frappe.db.set_value(
+ "Maintenance Schedule Detail",
+ purpose.maintenance_schedule_detail,
+ "completion_status",
+ status,
+ )
+ frappe.db.set_value(
+ "Maintenance Schedule Detail",
+ purpose.maintenance_schedule_detail,
+ "actual_date",
+ actual_date,
+ )
def update_customer_issue(self, flag):
if not self.maintenance_schedule:
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index ba05355..a5a63c4 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -17,7 +17,8 @@
"work_details",
"work_done",
"prevdoc_doctype",
- "prevdoc_docname"
+ "prevdoc_docname",
+ "maintenance_schedule_detail"
],
"fields": [
{
@@ -49,6 +50,8 @@
"options": "Serial No"
},
{
+ "fetch_from": "item_code.description",
+ "fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
@@ -56,7 +59,6 @@
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"print_width": "300px",
- "reqd": 1,
"width": "300px"
},
{
@@ -103,12 +105,19 @@
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "maintenance_schedule_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Maintenance Schedule Detail",
+ "options": "Maintenance Schedule Detail"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-02-27 11:09:33.114458",
+ "modified": "2024-01-05 21:46:53.239830",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py
index 3686941..1d4dab2 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py
@@ -14,9 +14,10 @@
if TYPE_CHECKING:
from frappe.types import DF
- description: DF.TextEditor
+ description: DF.TextEditor | None
item_code: DF.Link | None
item_name: DF.Data | None
+ maintenance_schedule_detail: DF.Data | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py
index 84be2be..7a0641d 100644
--- a/erpnext/patches/v11_0/create_department_records_for_each_company.py
+++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py
@@ -35,7 +35,7 @@
# append list of new department for each company
comp_dict[company.name][department.name] = copy_doc.name
- rebuild_tree("Department", "parent_department")
+ rebuild_tree("Department")
doctypes = ["Asset", "Employee", "Payroll Entry", "Staffing Plan", "Job Opening"]
for d in doctypes:
diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py
index c863bb7..8d9c8d8 100644
--- a/erpnext/patches/v11_0/make_location_from_warehouse.py
+++ b/erpnext/patches/v11_0/make_location_from_warehouse.py
@@ -27,7 +27,7 @@
except frappe.DuplicateEntryError:
continue
- rebuild_tree("Location", "parent_location")
+ rebuild_tree("Location")
def get_parent_warehouse_name(warehouse):
diff --git a/erpnext/patches/v11_0/rebuild_tree_for_company.py b/erpnext/patches/v11_0/rebuild_tree_for_company.py
index fc06c5d..a9b2344 100644
--- a/erpnext/patches/v11_0/rebuild_tree_for_company.py
+++ b/erpnext/patches/v11_0/rebuild_tree_for_company.py
@@ -4,4 +4,4 @@
def execute():
frappe.reload_doc("setup", "doctype", "company")
- rebuild_tree("Company", "parent_company")
+ rebuild_tree("Company")
diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
index 96daba7..67eb915 100644
--- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
+++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
@@ -41,4 +41,4 @@
}
).insert(ignore_permissions=True)
- rebuild_tree("Supplier Group", "parent_supplier_group")
+ rebuild_tree("Supplier Group")
diff --git a/erpnext/patches/v11_0/update_department_lft_rgt.py b/erpnext/patches/v11_0/update_department_lft_rgt.py
index 778392e..380ca4d 100644
--- a/erpnext/patches/v11_0/update_department_lft_rgt.py
+++ b/erpnext/patches/v11_0/update_department_lft_rgt.py
@@ -18,4 +18,4 @@
)
)
- rebuild_tree("Department", "parent_department")
+ rebuild_tree("Department")
diff --git a/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py b/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
index 2879e57..b70548c 100644
--- a/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
+++ b/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
@@ -4,5 +4,5 @@
def execute():
subscription = frappe.qb.DocType("Subscription")
frappe.qb.update(subscription).set(
- subscription.generate_invoice_at, "Beginning of the currency subscription period"
+ subscription.generate_invoice_at, "Beginning of the current subscription period"
).where(subscription.generate_invoice_at_period_start == 1).run()
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 9427c38..4d8f683 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -454,7 +454,7 @@
item.weight_uom = '';
item.conversion_factor = 0;
- if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
+ if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
update_stock = cint(me.frm.doc.update_stock);
show_batch_dialog = update_stock;
@@ -545,7 +545,7 @@
},
() => me.toggle_conversion_factor(item),
() => {
- if (show_batch_dialog)
+ if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
.then((r) => {
if (r.message &&
@@ -1239,6 +1239,20 @@
}
}
+ sync_bundle_data() {
+ let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
+
+ if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
+ const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+ barcode_scanner.sync_bundle_data();
+ barcode_scanner.remove_item_from_localstorage();
+ }
+ }
+
+ before_save(doc) {
+ this.sync_bundle_data();
+ }
+
service_start_date(frm, cdt, cdn) {
var child = locals[cdt][cdn];
@@ -1576,6 +1590,18 @@
return item_list;
}
+ items_delete() {
+ this.update_localstorage_scanned_data();
+ }
+
+ update_localstorage_scanned_data() {
+ let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
+ if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
+ const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+ barcode_scanner.update_localstorage_scanned_data();
+ }
+ }
+
_set_values_for_item_list(children) {
const items_rule_dict = {};
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 866e94f..598167b 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -865,16 +865,20 @@
}
if (opts.source_doctype) {
+ let data_fields = [];
+ if(opts.source_doctype == "Purchase Receipt") {
+ data_fields.push({
+ fieldname: 'merge_taxes',
+ fieldtype: 'Check',
+ label: __('Merge taxes from multiple documents'),
+ });
+ }
const d = new frappe.ui.form.MultiSelectDialog({
doctype: opts.source_doctype,
target: opts.target,
date_field: opts.date_field || undefined,
setters: opts.setters,
- data_fields: [{
- fieldname: 'merge_taxes',
- fieldtype: 'Check',
- label: __('Merge taxes from multiple documents'),
- }],
+ data_fields: data_fields,
get_query: opts.get_query,
add_filters_group: 1,
allow_child_item_selection: opts.allow_child_item_selection,
@@ -888,7 +892,10 @@
return;
}
opts.source_name = values;
- opts.args = args;
+ if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") {
+ // args contains filtered child docnames
+ opts.args = args;
+ }
d.dialog.hide();
_map();
},
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a1ebfe9..cf7fab8 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -7,8 +7,6 @@
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
this.barcode_field = opts.barcode_field || "barcode";
- this.serial_no_field = opts.serial_no_field || "serial_no";
- this.batch_no_field = opts.batch_no_field || "batch_no";
this.uom_field = opts.uom_field || "uom";
this.qty_field = opts.qty_field || "qty";
// field name on row which defines max quantity to be scanned e.g. picklist
@@ -84,6 +82,7 @@
update_table(data) {
return new Promise((resolve, reject) => {
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
+ frappe.flags.trigger_from_barcode_scanner = true;
const {item_code, barcode, batch_no, serial_no, uom} = data;
@@ -106,50 +105,38 @@
this.frm.has_items = false;
}
- if (this.is_duplicate_serial_no(row, serial_no)) {
+ if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
this.clean_up();
reject();
return;
}
frappe.run_serially([
- () => this.set_selector_trigger_flag(data),
- () => this.set_serial_no(row, serial_no),
- () => this.set_batch_no(row, batch_no),
+ () => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
() => this.set_barcode(row, barcode),
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty);
}),
() => this.set_barcode_uom(row, uom),
() => this.clean_up(),
- () => this.revert_selector_flag(),
- () => resolve(row)
+ () => resolve(row),
+ () => {
+ if (row.serial_and_batch_bundle && !this.frm.is_new()) {
+ this.frm.save();
+ }
+
+ frappe.flags.trigger_from_barcode_scanner = false;
+ }
]);
});
}
- // batch and serial selector is reduandant when all info can be added by scan
- // this flag on item row is used by transaction.js to avoid triggering selector
- set_selector_trigger_flag(data) {
- const {has_batch_no, has_serial_no} = data;
-
- const require_selecting_batch = has_batch_no;
- const require_selecting_serial = has_serial_no;
-
- if (!(require_selecting_batch || require_selecting_serial)) {
- frappe.flags.hide_serial_batch_dialog = true;
- }
- }
-
- revert_selector_flag() {
- frappe.flags.hide_serial_batch_dialog = false;
- }
-
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
const item_data = {item_code: item_code};
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
+ frappe.flags.trigger_from_barcode_scanner = true;
await frappe.model.set_value(row.doctype, row.name, item_data);
return value;
};
@@ -158,8 +145,6 @@
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
increment(value).then((value) => resolve(value));
});
- } else if (this.frm.has_items) {
- this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
} else {
increment().then((value) => resolve(value));
}
@@ -182,9 +167,8 @@
frappe.model.set_value(row.doctype, row.name, item_data);
frappe.run_serially([
- () => this.set_batch_no(row, this.dialog.get_value("batch_no")),
() => this.set_barcode(row, this.dialog.get_value("barcode")),
- () => this.set_serial_no(row, this.dialog.get_value("serial_no")),
+ () => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
() => this.add_child_for_remaining_qty(row),
() => this.clean_up()
]);
@@ -338,32 +322,144 @@
}
}
- async set_serial_no(row, serial_no) {
- if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
- const existing_serial_nos = row[this.serial_no_field];
- let new_serial_nos = "";
-
- if (!!existing_serial_nos) {
- new_serial_nos = existing_serial_nos + "\n" + serial_no;
- } else {
- new_serial_nos = serial_no;
- }
- await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
+ async set_serial_and_batch(row, item_code, serial_no, batch_no) {
+ if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+ this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
+ } else if(row.serial_and_batch_bundle) {
+ frappe.call({
+ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
+ args: {
+ bundle_id: row.serial_and_batch_bundle,
+ serial_no: serial_no,
+ batch_no: batch_no,
+ },
+ })
}
}
+ get_key_for_localstorage() {
+ let parts = this.frm.doc.name.split("-");
+ return parts[parts.length - 1] + this.frm.doc.doctype;
+ }
+
+ update_localstorage_scanned_data() {
+ let docname = this.frm.doc.name
+ if (localStorage[docname]) {
+ let items = JSON.parse(localStorage[docname]);
+ let existing_items = this.frm.doc.items.map(d => d.item_code);
+ if (!existing_items.length) {
+ localStorage.removeItem(docname);
+ return;
+ }
+
+ for (let item_code in items) {
+ if (!existing_items.includes(item_code)) {
+ delete items[item_code];
+ }
+ }
+
+ localStorage[docname] = JSON.stringify(items);
+ }
+ }
+
+ async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
+ let docname = this.frm.doc.name
+
+ let entries = JSON.parse(localStorage.getItem(docname));
+ if (!entries) {
+ entries = {};
+ }
+
+ let key = item_code;
+ if (!entries[key]) {
+ entries[key] = [];
+ }
+
+ let existing_row = [];
+ if (!serial_no && batch_no) {
+ existing_row = entries[key].filter((e) => e.batch_no === batch_no);
+ if (existing_row.length) {
+ existing_row[0].qty += 1;
+ }
+ } else if (serial_no) {
+ existing_row = entries[key].filter((e) => e.serial_no === serial_no);
+ if (existing_row.length) {
+ frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
+ }
+ }
+
+ if (!existing_row.length) {
+ entries[key].push({
+ "serial_no": serial_no,
+ "batch_no": batch_no,
+ "qty": 1
+ });
+ }
+
+ localStorage.setItem(docname, JSON.stringify(entries));
+
+ // Auto remove from localstorage after 1 hour
+ setTimeout(() => {
+ localStorage.removeItem(docname);
+ }, 3600000)
+ }
+
+ remove_item_from_localstorage() {
+ let docname = this.frm.doc.name;
+ if (localStorage[docname]) {
+ localStorage.removeItem(docname);
+ }
+ }
+
+ async sync_bundle_data() {
+ let docname = this.frm.doc.name;
+
+ if (localStorage[docname]) {
+ let entries = JSON.parse(localStorage[docname]);
+ if (entries) {
+ for (let entry in entries) {
+ let row = this.frm.doc.items.filter((item) => {
+ if (item.item_code === entry) {
+ return true;
+ }
+ })[0];
+
+ if (row) {
+ this.create_serial_and_batch_bundle(row, entries, entry)
+ .then(() => {
+ if (!entries) {
+ localStorage.removeItem(docname);
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+
+ async create_serial_and_batch_bundle(row, entries, key) {
+ frappe.call({
+ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
+ args: {
+ entries: entries[key],
+ child_row: row,
+ doc: this.frm.doc,
+ warehouse: row.warehouse,
+ do_not_save: 1
+ },
+ callback: function(r) {
+ row.serial_and_batch_bundle = r.message.name;
+ delete entries[key];
+ }
+ })
+ }
+
async set_barcode_uom(row, uom) {
if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
}
}
- async set_batch_no(row, batch_no) {
- if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
- await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
- }
- }
-
async set_barcode(row, barcode) {
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
@@ -379,13 +475,52 @@
}
}
- is_duplicate_serial_no(row, serial_no) {
- const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
+ is_duplicate_serial_no(row, item_code, serial_no) {
+ if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+ let is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
+ if (is_duplicate) {
+ this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+ }
- if (is_duplicate) {
- this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+ return is_duplicate;
+ } else if (row.serial_and_batch_bundle) {
+ this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
+ if (r.message) {
+ this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+ }
+
+ return r.message;
+ })
}
- return is_duplicate;
+ }
+
+ async check_duplicate_serial_no_in_db(row, serial_no, response) {
+ frappe.call({
+ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
+ args: {
+ serial_no: serial_no,
+ bundle_id: row.serial_and_batch_bundle
+ },
+ callback(r) {
+ response(r);
+ }
+ })
+ }
+
+ check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
+ let docname = this.frm.doc.name
+ let entries = JSON.parse(localStorage.getItem(docname));
+
+ if (!entries) {
+ return false;
+ }
+
+ let existing_row = [];
+ if (entries[item_code]) {
+ existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
+ }
+
+ return existing_row.length;
}
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
index 4bc98b9..df2c49b 100644
--- a/erpnext/setup/demo.py
+++ b/erpnext/setup/demo.py
@@ -149,6 +149,11 @@
invoice.set_posting_time = 1
invoice.posting_date = order.transaction_date
invoice.due_date = order.transaction_date
+ invoice.bill_date = order.transaction_date
+
+ if invoice.get("payment_schedule"):
+ invoice.payment_schedule[0].due_date = order.transaction_date
+
invoice.update_stock = 1
invoice.submit()
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 9897847..ec953b8 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -249,7 +249,7 @@
if frappe.flags.parent_company_changed:
from frappe.utils.nestedset import rebuild_tree
- rebuild_tree("Company", "parent_company")
+ rebuild_tree("Company")
frappe.clear_cache()
@@ -397,7 +397,7 @@
frappe.local.flags.ignore_update_nsm = True
make_records(records)
frappe.local.flags.ignore_update_nsm = False
- rebuild_tree("Department", "parent_department")
+ rebuild_tree("Department")
def validate_coa_input(self):
if self.create_chart_of_accounts_based_on == "Existing Company":
diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json
index 1143ccb..daf2df5 100644
--- a/erpnext/setup/doctype/employee/employee.json
+++ b/erpnext/setup/doctype/employee/employee.json
@@ -616,8 +616,8 @@
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
- "no_copy": 1,
"mandatory_depends_on": "eval:doc.status == \"Left\"",
+ "no_copy": 1,
"oldfieldname": "relieving_date",
"oldfieldtype": "Date"
},
@@ -822,12 +822,14 @@
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
+ "is_tree": 1,
"links": [],
- "modified": "2023-10-04 10:57:05.174592",
+ "modified": "2024-01-03 17:36:20.984421",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
"naming_rule": "By \"Naming Series\" field",
+ "nsm_parent_field": "reports_to",
"owner": "Administrator",
"permissions": [
{
@@ -860,7 +862,6 @@
"read": 1,
"report": 1,
"role": "HR Manager",
- "set_user_permissions": 1,
"share": 1,
"write": 1
}
@@ -871,4 +872,4 @@
"sort_order": "DESC",
"states": [],
"title_field": "employee_name"
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/item_group/test_item_group.py b/erpnext/setup/doctype/item_group/test_item_group.py
index 11bc9b9..d95199d 100644
--- a/erpnext/setup/doctype/item_group/test_item_group.py
+++ b/erpnext/setup/doctype/item_group/test_item_group.py
@@ -79,7 +79,7 @@
group_b.save()
def test_rebuild_tree(self):
- rebuild_tree("Item Group", "parent_item_group")
+ rebuild_tree("Item Group")
self.test_basic_tree()
def move_it_back(self):
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index 45bf012..bd16d69 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -59,7 +59,7 @@
else:
from frappe.utils.nestedset import rebuild_tree
- rebuild_tree("Warehouse", "parent_warehouse")
+ rebuild_tree("Warehouse")
else:
account = frappe.db.sql(
"""
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 3abd1d9..dae4289 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1518,6 +1518,25 @@
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
)
+ def test_internal_transfer_for_non_stock_item(self):
+ from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+ from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+
+ item = make_item(properties={"is_stock_item": 0}).name
+ warehouse = "_Test Warehouse - _TC"
+ target = "Stores - _TC"
+ company = "_Test Company"
+ customer = create_internal_customer(represents_company=company)
+ rate = 100
+
+ so = make_sales_order(item_code=item, qty=1, rate=rate, customer=customer, warehouse=warehouse)
+ dn = make_delivery_note(so.name)
+ dn.items[0].target_warehouse = target
+ dn.save().submit()
+
+ self.assertEqual(so.items[0].rate, rate)
+ self.assertEqual(dn.items[0].rate, so.items[0].rate)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 60624d4..d5eef5a 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -305,7 +305,7 @@
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
filter_dimensions = []
for row in dimensions:
- if row.type_of_transaction:
+ if row.type_of_transaction and row.type_of_transaction != "Both":
if (
row.type_of_transaction == "Inward"
if doc.docstatus == 1
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 33394e5..361c2f8 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -429,6 +429,14 @@
)
warehouse = create_warehouse("Negative Stock Warehouse")
+
+ doc = make_stock_entry(item_code=item_code, source=warehouse, qty=10, do_not_submit=True)
+ doc.items[0].inv_site = "Site 1"
+ self.assertRaises(frappe.ValidationError, doc.submit)
+ doc.reload()
+ if doc.docstatus == 1:
+ doc.cancel()
+
doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True)
doc.items[0].to_inv_site = "Site 1"
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 218406f..620b960 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -729,19 +729,13 @@
def before_cancel(self):
self.delink_serial_and_batch_bundle()
- self.clear_table()
def delink_serial_and_batch_bundle(self):
- self.voucher_no = None
-
sles = frappe.get_all("Stock Ledger Entry", filters={"serial_and_batch_bundle": self.name})
for sle in sles:
frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_and_batch_bundle", None)
- def clear_table(self):
- self.set("entries", [])
-
@property
def child_table(self):
if self.voucher_type == "Job Card":
@@ -876,7 +870,6 @@
self.validate_voucher_no_docstatus()
self.delink_refernce_from_voucher()
self.delink_reference_from_batch()
- self.clear_table()
@frappe.whitelist()
def add_serial_batch(self, data):
@@ -1011,13 +1004,17 @@
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
serial_nos = [d.get("serial_no") 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")]
+ serial_nos = list(set(serial_nos) - set(existing_serial_nos))
+
+ if not serial_nos:
+ return
serial_nos_details = []
user = frappe.session.user
for serial_no in serial_nos:
- if frappe.db.exists("Serial No", serial_no):
- continue
-
serial_nos_details.append(
(
serial_no,
@@ -1053,9 +1050,16 @@
def make_batch_nos(item_code, batch_nos):
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
-
batch_nos = [d.get("batch_no") for d in batch_nos if d.get("batch_no")]
+ existing_batches = frappe.get_all("Batch", filters={"name": ("in", batch_nos)})
+
+ existing_batches = [d.get("name") for d in existing_batches if d.get("name")]
+
+ batch_nos = list(set(batch_nos) - set(existing_batches))
+ if not batch_nos:
+ return
+
batch_nos_details = []
user = frappe.session.user
for batch_no in batch_nos:
@@ -1156,7 +1160,7 @@
@frappe.whitelist()
-def add_serial_batch_ledgers(entries, child_row, doc, warehouse) -> object:
+def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object:
if isinstance(child_row, str):
child_row = frappe._dict(parse_json(child_row))
@@ -1170,20 +1174,23 @@
if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
sb_doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
else:
- sb_doc = create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
+ sb_doc = create_serial_batch_no_ledgers(
+ entries, child_row, parent_doc, warehouse, do_not_save=do_not_save
+ )
return sb_doc
-def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
+def create_serial_batch_no_ledgers(
+ entries, child_row, parent_doc, warehouse=None, do_not_save=False
+) -> object:
warehouse = warehouse or (
child_row.rejected_warehouse if child_row.is_rejected else child_row.warehouse
)
- type_of_transaction = child_row.type_of_transaction
+ type_of_transaction = get_type_of_transaction(parent_doc, child_row)
if parent_doc.get("doctype") == "Stock Entry":
- type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
doc = frappe.get_doc(
@@ -1214,13 +1221,30 @@
doc.save()
- frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
+ if do_not_save:
+ frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
frappe.msgprint(_("Serial and Batch Bundle created"), alert=True)
return doc
+def get_type_of_transaction(parent_doc, child_row):
+ type_of_transaction = child_row.type_of_transaction
+ if parent_doc.get("doctype") == "Stock Entry":
+ type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
+
+ if not type_of_transaction:
+ type_of_transaction = "Outward"
+ if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]:
+ type_of_transaction = "Inward"
+
+ if parent_doc.get("is_return"):
+ type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
+
+ return type_of_transaction
+
+
def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
doc.voucher_detail_no = child_row.name
@@ -1247,6 +1271,25 @@
return doc
+@frappe.whitelist()
+def update_serial_or_batch(bundle_id, serial_no=None, batch_no=None):
+ if batch_no and not serial_no:
+ if qty := frappe.db.get_value(
+ "Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty"
+ ):
+ frappe.db.set_value(
+ "Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty", qty + 1
+ )
+ return
+
+ doc = frappe.get_cached_doc("Serial and Batch Bundle", bundle_id)
+ if not serial_no and not batch_no:
+ return
+
+ doc.append("entries", {"serial_no": serial_no, "batch_no": batch_no, "qty": 1})
+ doc.save(ignore_permissions=True)
+
+
def get_serial_and_batch_ledger(**kwargs):
kwargs = frappe._dict(kwargs)
@@ -2032,3 +2075,8 @@
@frappe.whitelist()
def get_batch_no_from_serial_no(serial_no):
return frappe.get_cached_value("Serial No", serial_no, "batch_no")
+
+
+@frappe.whitelist()
+def is_duplicate_serial_no(bundle_id, serial_no):
+ return frappe.db.exists("Serial and Batch Entry", {"parent": bundle_id, "serial_no": serial_no})
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 1975747..0d453fb 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -10,6 +10,8 @@
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
add_serial_batch_ledgers,
+ make_batch_nos,
+ make_serial_nos,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -481,6 +483,38 @@
docstatus = frappe.db.get_value("Serial and Batch Bundle", bundle, "docstatus")
self.assertEqual(docstatus, 2)
+ def test_batch_duplicate_entry(self):
+ item_code = make_item(properties={"has_batch_no": 1}).name
+
+ batch_id = "TEST-BATTCCH-VAL-00001"
+ batch_nos = [{"batch_no": batch_id, "qty": 1}]
+
+ make_batch_nos(item_code, batch_nos)
+ self.assertTrue(frappe.db.exists("Batch", batch_id))
+
+ batch_id = "TEST-BATTCCH-VAL-00001"
+ batch_nos = [{"batch_no": batch_id, "qty": 1}]
+
+ # Shouldn't throw duplicate entry error
+ make_batch_nos(item_code, batch_nos)
+ self.assertTrue(frappe.db.exists("Batch", batch_id))
+
+ def test_serial_no_duplicate_entry(self):
+ item_code = make_item(properties={"has_serial_no": 1}).name
+
+ serial_no_id = "TEST-SNID-VAL-00001"
+ serial_nos = [{"serial_no": serial_no_id, "qty": 1}]
+
+ make_serial_nos(item_code, serial_nos)
+ self.assertTrue(frappe.db.exists("Serial No", serial_no_id))
+
+ serial_no_id = "TEST-SNID-VAL-00001"
+ serial_nos = [{"batch_no": serial_no_id, "qty": 1}]
+
+ # Shouldn't throw duplicate entry error
+ make_serial_nos(item_code, serial_nos)
+ self.assertTrue(frappe.db.exists("Serial No", serial_no_id))
+
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2ccee94..bccbc28 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -24,6 +24,7 @@
import erpnext
from erpnext.accounts.general_ledger import process_gl_map
+from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
from erpnext.manufacturing.doctype.bom.bom import (
add_additional_cost,
@@ -208,7 +209,6 @@
self.validate_bom()
self.set_process_loss_qty()
self.validate_purchase_order()
- self.validate_subcontracting_order()
if self.purpose in ("Manufacture", "Repack"):
self.mark_finished_and_scrap_items()
@@ -274,6 +274,7 @@
return False
def on_submit(self):
+ self.validate_closed_subcontracting_order()
self.update_stock_ledger()
self.update_work_order()
self.validate_subcontract_order()
@@ -294,6 +295,7 @@
self.set_material_request_transfer_status("Completed")
def on_cancel(self):
+ self.validate_closed_subcontracting_order()
self.update_subcontract_order_supplied_items()
self.update_subcontracting_order_status()
@@ -1203,19 +1205,9 @@
)
)
- def validate_subcontracting_order(self):
- if self.get("subcontracting_order") and self.purpose in [
- "Send to Subcontractor",
- "Material Transfer",
- ]:
- sco_status = frappe.db.get_value("Subcontracting Order", self.subcontracting_order, "status")
-
- if sco_status == "Closed":
- frappe.throw(
- _("Cannot create Stock Entry against a closed Subcontracting Order {0}.").format(
- self.subcontracting_order
- )
- )
+ def validate_closed_subcontracting_order(self):
+ if self.get("subcontracting_order"):
+ check_on_hold_or_closed_status("Subcontracting Order", self.subcontracting_order)
def mark_finished_and_scrap_items(self):
if self.purpose != "Repack" and any(
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 6e7af68..277ca01 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -111,16 +111,20 @@
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"company": self.company,
+ "sle": self.name,
}
)
sle = get_previous_sle(kwargs, extra_cond=extra_cond)
+ qty_after_transaction = 0.0
+ flt_precision = cint(frappe.db.get_default("float_precision")) or 2
if sle:
- flt_precision = cint(frappe.db.get_default("float_precision")) or 2
- diff = sle.qty_after_transaction + flt(self.actual_qty)
- diff = flt(diff, flt_precision)
- if diff < 0 and abs(diff) > 0.0001:
- self.throw_validation_error(diff, dimensions)
+ qty_after_transaction = sle.qty_after_transaction
+
+ diff = qty_after_transaction + flt(self.actual_qty)
+ diff = flt(diff, flt_precision)
+ if diff < 0 and abs(diff) > 0.0001:
+ self.throw_validation_error(diff, dimensions)
def throw_validation_error(self, diff, dimensions):
dimension_msg = _(", with the inventory {0}: {1}").format(
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 39df227..4cfe5d8 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -209,7 +209,7 @@
frappe.db.set_value(
"Serial and Batch Bundle",
{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
- {"is_cancelled": 1, "voucher_no": ""},
+ {"is_cancelled": 1},
)
if self.sle.serial_and_batch_bundle:
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index bd0d469..4b0e284 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -591,6 +591,13 @@
as_dict=True,
)
if batch_no_data:
+ if frappe.get_cached_value("Item", batch_no_data.item_code, "has_serial_no"):
+ frappe.throw(
+ _(
+ "Batch No {0} is linked with Item {1} which has serial no. Please scan serial no instead."
+ ).format(search_value, batch_no_data.item_code)
+ )
+
_update_item_info(batch_no_data)
set_cache(batch_no_data)
return batch_no_data
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
index 587a3b4..4c8a0ad 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -101,9 +101,32 @@
},
refresh: function (frm) {
+ if (frm.doc.docstatus == 1 && frm.has_perm("submit")) {
+ if (frm.doc.status == "Closed") {
+ frm.add_custom_button(__('Re-open'), () => frm.events.update_subcontracting_order_status(frm), __("Status"));
+ } else if(flt(frm.doc.per_received, 2) < 100) {
+ frm.add_custom_button(__('Close'), () => frm.events.update_subcontracting_order_status(frm, "Closed"), __("Status"));
+ }
+ }
+
frm.trigger('get_materials_from_supplier');
},
+ update_subcontracting_order_status(frm, status) {
+ frappe.call({
+ method: "erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.update_subcontracting_order_status",
+ args: {
+ sco: frm.doc.name,
+ status: status,
+ },
+ callback: function (r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ },
+ });
+ },
+
get_materials_from_supplier: function (frm) {
let sco_rm_details = [];
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
index 28c52c9..507e233 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
@@ -370,7 +370,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
- "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled",
+ "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled\nClosed",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
@@ -454,7 +454,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:18:17.782538",
+ "modified": "2024-01-03 20:56:04.670380",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 0fe8c13..daccbbb 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -7,7 +7,7 @@
from frappe.utils import flt
from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
-from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status
+from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.stock_balance import update_bin_qty
from erpnext.stock.utils import get_bin
@@ -68,6 +68,7 @@
"Material Transferred",
"Partial Material Transferred",
"Cancelled",
+ "Closed",
]
supplied_items: DF.Table[SubcontractingOrderSuppliedItem]
supplier: DF.Link
@@ -112,16 +113,10 @@
def on_submit(self):
self.update_prevdoc_status()
- self.update_requested_qty()
- self.update_ordered_qty_for_subcontracting()
- self.update_reserved_qty_for_subcontracting()
self.update_status()
def on_cancel(self):
self.update_prevdoc_status()
- self.update_requested_qty()
- self.update_ordered_qty_for_subcontracting()
- self.update_reserved_qty_for_subcontracting()
self.update_status()
def validate_purchase_order_for_subcontracting(self):
@@ -277,6 +272,9 @@
self.set_missing_values()
def update_status(self, status=None, update_modified=True):
+ if self.status == "Closed" and self.status != status:
+ check_on_hold_or_closed_status("Purchase Order", self.purchase_order)
+
if self.docstatus >= 1 and not status:
if self.docstatus == 1:
if self.status == "Draft":
@@ -285,11 +283,6 @@
status = "Completed"
elif self.per_received > 0 and self.per_received < 100:
status = "Partially Received"
- for item in self.supplied_items:
- if not item.returned_qty or (item.supplied_qty - item.consumed_qty - item.returned_qty) > 0:
- break
- else:
- status = "Closed"
else:
total_required_qty = total_supplied_qty = 0
for item in self.supplied_items:
@@ -304,13 +297,12 @@
elif self.docstatus == 2:
status = "Cancelled"
- if status:
- frappe.db.set_value(
- "Subcontracting Order", self.name, "status", status, update_modified=update_modified
- )
+ if status and self.status != status:
+ self.db_set("status", status, update_modified=update_modified)
- if status == "Closed":
- update_po_status("Closed", self.purchase_order)
+ self.update_requested_qty()
+ self.update_ordered_qty_for_subcontracting()
+ self.update_reserved_qty_for_subcontracting()
@frappe.whitelist()
@@ -357,8 +349,8 @@
@frappe.whitelist()
-def update_subcontracting_order_status(sco):
+def update_subcontracting_order_status(sco, status=None):
if isinstance(sco, str):
sco = frappe.get_doc("Subcontracting Order", sco)
- sco.update_status()
+ sco.update_status(status)
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
index 7ca1264..ec54944 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
@@ -10,7 +10,7 @@
"Completed": "green",
"Partial Material Transferred": "purple",
"Material Transferred": "blue",
- "Closed": "red",
+ "Closed": "green",
"Cancelled": "red",
};
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 37dabf1..6c0ee45 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -95,14 +95,14 @@
self.assertEqual(sco.status, "Partially Received")
# Closed
- ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
- ste.save()
- ste.submit()
- sco.load_from_db()
+ sco.update_status("Closed")
self.assertEqual(sco.status, "Closed")
- ste.cancel()
- sco.load_from_db()
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ self.assertRaises(frappe.exceptions.ValidationError, scr.submit)
+ sco.update_status()
self.assertEqual(sco.status, "Partially Received")
+ scr.cancel()
# Completed
scr = make_subcontracting_receipt(sco.name)
@@ -564,7 +564,6 @@
sco.load_from_db()
- self.assertEqual(sco.status, "Closed")
self.assertEqual(sco.supplied_items[0].returned_qty, 5)
def test_ordered_qty_for_subcontracting_order(self):
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index 575c4ed..0535799 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -93,7 +93,8 @@
get_query_filters: {
docstatus: 1,
per_received: ['<', 100],
- company: frm.doc.company
+ company: frm.doc.company,
+ status: ['!=', 'Closed'],
}
});
}, __('Get Items From'));
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 52bf13c..7c2a1f1 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -8,6 +8,7 @@
import erpnext
from erpnext.accounts.utils import get_account_currency
+from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.stock_ledger import get_valuation_rate
@@ -142,6 +143,7 @@
self.get_current_stock()
def on_submit(self):
+ self.validate_closed_subcontracting_order()
self.validate_available_qty_for_consumption()
self.update_status_updater_args()
self.update_prevdoc_status()
@@ -165,6 +167,7 @@
"Repost Item Valuation",
"Serial and Batch Bundle",
)
+ self.validate_closed_subcontracting_order()
self.update_status_updater_args()
self.update_prevdoc_status()
self.set_consumed_qty_in_subcontract_order()
@@ -175,6 +178,11 @@
self.update_status()
self.delete_auto_created_batches()
+ def validate_closed_subcontracting_order(self):
+ for item in self.items:
+ if item.subcontracting_order:
+ check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order)
+
def validate_items_qty(self):
for item in self.items:
if not (item.qty or item.rejected_qty):
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index d05d0d9..84c71ba 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -600,7 +600,7 @@
Course Enrollment {0} does not exists,Die Kursanmeldung {0} existiert nicht,
Course Schedule,Kurstermine,
Course: ,Kurs:,
-Cr,Haben,
+Cr,H,
Create,Erstellen,
Create BOM,Stückliste anlegen,
Create Delivery Trip,Erstelle Auslieferungsfahrt,
@@ -3401,7 +3401,7 @@
Doctype,DocType,
Document {0} successfully uncleared,Dokument {0} wurde nicht erfolgreich gelöscht,
Download Template,Vorlage herunterladen,
-Dr,Soll,
+Dr,S,
Due Date,Fälligkeitsdatum,
Duplicate,Duplizieren,
Duplicate Project with Tasks,Projekt mit Aufgaben duplizieren,
@@ -7370,6 +7370,7 @@
Sample Retention Warehouse,Beispiel Retention Warehouse,
Default Valuation Method,Standard-Bewertungsmethode,
Show Barcode Field,Anzeigen Barcode-Feld,
+Show Balances in Chart Of Accounts,Saldo in Kontenplan anzeigen,
Convert Item Description to Clean HTML,Elementbeschreibung in HTML bereinigen,
Allow Negative Stock,Negativen Lagerbestand zulassen,
Automatically Set Serial Nos based on FIFO,Automatisch Seriennummern auf Basis FIFO einstellen,
@@ -8812,7 +8813,6 @@
Field Mapping,Feldzuordnung,
Not Specified,Keine Angabe,
Update Type,Aktualisierungsart,
-Dr,Soll,
End Time,Endzeit,
Fetching...,Abrufen ...,
"It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.",
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index 679d5bd..9678488 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -15,18 +15,15 @@
length_of_data = len(deserialized_data)
- if length_of_data > 10:
- frappe.msgprint(
- _("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
- )
- frappe.enqueue(
- job,
- deserialized_data=deserialized_data,
- from_doctype=from_doctype,
- to_doctype=to_doctype,
- )
- else:
- job(deserialized_data, from_doctype, to_doctype)
+ frappe.msgprint(
+ _("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
+ )
+ frappe.enqueue(
+ job,
+ deserialized_data=deserialized_data,
+ from_doctype=from_doctype,
+ to_doctype=to_doctype,
+ )
@frappe.whitelist()