mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-28 01:14:46 +00:00
Merge pull request #45358 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
"advance_amount",
|
"advance_amount",
|
||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"exchange_gain_loss",
|
"exchange_gain_loss",
|
||||||
"ref_exchange_rate"
|
"ref_exchange_rate",
|
||||||
|
"difference_posting_date"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
"width": "180px"
|
"width": "180px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 3,
|
"columns": 2,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 3,
|
"columns": 2,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -111,13 +112,20 @@
|
|||||||
"label": "Reference Exchange Rate",
|
"label": "Reference Exchange Rate",
|
||||||
"non_negative": 1,
|
"non_negative": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "difference_posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Difference Posting Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-26 15:47:28.167371",
|
"modified": "2024-12-20 12:04:46.729972",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Advance",
|
"name": "Purchase Invoice Advance",
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"advance_amount",
|
"advance_amount",
|
||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"exchange_gain_loss",
|
"exchange_gain_loss",
|
||||||
"ref_exchange_rate"
|
"ref_exchange_rate",
|
||||||
|
"difference_posting_date"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
"width": "250px"
|
"width": "250px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 3,
|
"columns": 2,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 3,
|
"columns": 2,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -112,13 +113,20 @@
|
|||||||
"label": "Reference Exchange Rate",
|
"label": "Reference Exchange Rate",
|
||||||
"non_negative": 1,
|
"non_negative": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "difference_posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Difference Posting Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-26 15:47:46.911595",
|
"modified": "2024-12-20 11:58:28.962370",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Advance",
|
"name": "Sales Invoice Advance",
|
||||||
|
|||||||
@@ -124,6 +124,9 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
cost_center = get_cost_center(inv)
|
cost_center = get_cost_center(inv)
|
||||||
tax_row.update({"cost_center": cost_center})
|
tax_row.update({"cost_center": cost_center})
|
||||||
|
|
||||||
|
if cint(tax_details.round_off_tax_amount):
|
||||||
|
inv.round_off_applicable_accounts_for_tax_withholding = tax_details.account_head
|
||||||
|
|
||||||
if inv.doctype == "Purchase Invoice":
|
if inv.doctype == "Purchase Invoice":
|
||||||
return tax_row, tax_deducted_on_advances, voucher_wise_amount
|
return tax_row, tax_deducted_on_advances, voucher_wise_amount
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -346,13 +346,14 @@ class AccountsController(TransactionBase):
|
|||||||
== 1
|
== 1
|
||||||
)
|
)
|
||||||
).run()
|
).run()
|
||||||
frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
|
frappe.qb.from_(gle).delete().where(
|
||||||
)
|
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
|
||||||
frappe.db.sql(
|
).run()
|
||||||
"delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
(self.doctype, self.name),
|
frappe.qb.from_(sle).delete().where(
|
||||||
)
|
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
|
||||||
|
).run()
|
||||||
|
|
||||||
def validate_return_against_account(self):
|
def validate_return_against_account(self):
|
||||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
||||||
@@ -1027,11 +1028,12 @@ class AccountsController(TransactionBase):
|
|||||||
def clear_unallocated_advances(self, childtype, parentfield):
|
def clear_unallocated_advances(self, childtype, parentfield):
|
||||||
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
|
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
|
||||||
|
|
||||||
frappe.db.sql(
|
doctype = frappe.qb.DocType(childtype)
|
||||||
"""delete from `tab{}` where parentfield={} and parent = {}
|
frappe.qb.from_(doctype).delete().where(
|
||||||
and allocated_amount = 0""".format(childtype, "%s", "%s"),
|
(doctype.parentfield == parentfield)
|
||||||
(parentfield, self.name),
|
& (doctype.parent == self.name)
|
||||||
)
|
& (doctype.allocated_amount == 0)
|
||||||
|
).run()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def apply_shipping_rule(self):
|
def apply_shipping_rule(self):
|
||||||
@@ -1082,6 +1084,7 @@ class AccountsController(TransactionBase):
|
|||||||
"advance_amount": flt(d.amount),
|
"advance_amount": flt(d.amount),
|
||||||
"allocated_amount": allocated_amount,
|
"allocated_amount": allocated_amount,
|
||||||
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
|
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
|
||||||
|
"difference_posting_date": self.posting_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.append("advances", advance_row)
|
self.append("advances", advance_row)
|
||||||
@@ -1332,7 +1335,6 @@ class AccountsController(TransactionBase):
|
|||||||
gain_loss_account = frappe.get_cached_value(
|
gain_loss_account = frappe.get_cached_value(
|
||||||
"Company", self.company, "exchange_gain_loss_account"
|
"Company", self.company, "exchange_gain_loss_account"
|
||||||
)
|
)
|
||||||
|
|
||||||
je = create_gain_loss_journal(
|
je = create_gain_loss_journal(
|
||||||
self.company,
|
self.company,
|
||||||
args.get("difference_posting_date") if args else self.posting_date,
|
args.get("difference_posting_date") if args else self.posting_date,
|
||||||
@@ -1445,6 +1447,7 @@ class AccountsController(TransactionBase):
|
|||||||
"Company", self.company, "exchange_gain_loss_account"
|
"Company", self.company, "exchange_gain_loss_account"
|
||||||
),
|
),
|
||||||
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
|
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
|
||||||
|
"difference_posting_date": d.get("difference_posting_date"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
lst.append(args)
|
lst.append(args)
|
||||||
@@ -1971,11 +1974,9 @@ class AccountsController(TransactionBase):
|
|||||||
for adv in self.advances:
|
for adv in self.advances:
|
||||||
consider_for_total_advance = True
|
consider_for_total_advance = True
|
||||||
if adv.reference_name == linked_doc_name:
|
if adv.reference_name == linked_doc_name:
|
||||||
frappe.db.sql(
|
doctype = frappe.qb.DocType(self.doctype + " Advance")
|
||||||
f"""delete from `tab{self.doctype} Advance`
|
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
|
||||||
where name = %s""",
|
|
||||||
adv.name,
|
|
||||||
)
|
|
||||||
consider_for_total_advance = False
|
consider_for_total_advance = False
|
||||||
|
|
||||||
if consider_for_total_advance:
|
if consider_for_total_advance:
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ def validate_returned_items(doc):
|
|||||||
for d in doc.get("items"):
|
for d in doc.get("items"):
|
||||||
key = d.item_code
|
key = d.item_code
|
||||||
raise_exception = False
|
raise_exception = False
|
||||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice"]:
|
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice", "POS Invoice"]:
|
||||||
field = frappe.scrub(doc.doctype) + "_item"
|
field = frappe.scrub(doc.doctype) + "_item"
|
||||||
if d.get(field):
|
if d.get(field):
|
||||||
key = (d.item_code, d.get(field))
|
key = (d.item_code, d.get(field))
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ class calculate_taxes_and_totals:
|
|||||||
self.doc = doc
|
self.doc = doc
|
||||||
frappe.flags.round_off_applicable_accounts = []
|
frappe.flags.round_off_applicable_accounts = []
|
||||||
|
|
||||||
|
if doc.get("round_off_applicable_accounts_for_tax_withholding"):
|
||||||
|
frappe.flags.round_off_applicable_accounts.append(
|
||||||
|
doc.round_off_applicable_accounts_for_tax_withholding
|
||||||
|
)
|
||||||
|
|
||||||
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
|
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
|
||||||
|
|
||||||
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from frappe import _, qb
|
|||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
|
from frappe.utils import add_days, flt, get_datetime, get_link_to_form, get_time, nowtime, today
|
||||||
|
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
from erpnext.controllers.queries import get_filters_cond
|
from erpnext.controllers.queries import get_filters_cond
|
||||||
@@ -275,24 +275,19 @@ class Project(Document):
|
|||||||
frappe.db.set_value("Project", new_name, "copied_from", new_name)
|
frappe.db.set_value("Project", new_name, "copied_from", new_name)
|
||||||
|
|
||||||
def send_welcome_email(self):
|
def send_welcome_email(self):
|
||||||
url = get_url(f"/project/?name={self.name}")
|
label = f"{self.project_name} ({self.name})"
|
||||||
messages = (
|
url = get_link_to_form(self.doctype, self.name, label)
|
||||||
_("You have been invited to collaborate on the project: {0}").format(self.name),
|
|
||||||
url,
|
|
||||||
_("Join"),
|
|
||||||
)
|
|
||||||
|
|
||||||
content = """
|
content = "<p>{}</p>".format(
|
||||||
<p>{0}.</p>
|
_("You have been invited to collaborate on the project: {0}").format(url)
|
||||||
<p><a href="{1}">{2}</a></p>
|
)
|
||||||
"""
|
|
||||||
|
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
if user.welcome_email_sent == 0:
|
if user.welcome_email_sent == 0:
|
||||||
frappe.sendmail(
|
frappe.sendmail(
|
||||||
user.user,
|
user.user,
|
||||||
subject=_("Project Collaboration Invitation"),
|
subject=_("Project Collaboration Invitation"),
|
||||||
content=content.format(*messages),
|
content=content,
|
||||||
)
|
)
|
||||||
user.welcome_email_sent = 1
|
user.welcome_email_sent = 1
|
||||||
|
|
||||||
|
|||||||
@@ -51,13 +51,18 @@ def search_by_term(search_term, warehouse, price_list):
|
|||||||
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
|
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
|
||||||
item.update({"actual_qty": item_stock_qty})
|
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(
|
price = frappe.get_list(
|
||||||
doctype="Item Price",
|
doctype="Item Price",
|
||||||
filters={
|
filters=price_filters,
|
||||||
"price_list": price_list,
|
fields=["uom", "currency", "price_list_rate", "batch_no"],
|
||||||
"item_code": item_code,
|
|
||||||
},
|
|
||||||
fields=["uom", "currency", "price_list_rate"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __sort(p):
|
def __sort(p):
|
||||||
|
|||||||
@@ -325,13 +325,16 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter_items({ search_term = "" } = {}) {
|
filter_items({ search_term = "" } = {}) {
|
||||||
|
const selling_price_list = this.events.get_frm().doc.selling_price_list;
|
||||||
|
|
||||||
if (search_term) {
|
if (search_term) {
|
||||||
search_term = search_term.toLowerCase();
|
search_term = search_term.toLowerCase();
|
||||||
|
|
||||||
// memoize
|
// memoize
|
||||||
this.search_index = this.search_index || {};
|
this.search_index = this.search_index || {};
|
||||||
if (this.search_index[search_term]) {
|
this.search_index[selling_price_list] = this.search_index[selling_price_list] || {};
|
||||||
const items = this.search_index[search_term];
|
if (this.search_index[selling_price_list][search_term]) {
|
||||||
|
const items = this.search_index[selling_price_list][search_term];
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.render_item_list(items);
|
this.render_item_list(items);
|
||||||
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
|
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
|
||||||
@@ -343,7 +346,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { items, serial_no, batch_no, barcode } = message;
|
const { items, serial_no, batch_no, barcode } = message;
|
||||||
if (search_term && !barcode) {
|
if (search_term && !barcode) {
|
||||||
this.search_index[search_term] = items;
|
this.search_index[selling_price_list][search_term] = items;
|
||||||
}
|
}
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.render_item_list(items);
|
this.render_item_list(items);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
from frappe.utils.data import get_url_to_list
|
||||||
from frappe.utils.nestedset import NestedSet, get_root_of
|
from frappe.utils.nestedset import NestedSet, get_root_of
|
||||||
|
|
||||||
from erpnext import get_default_currency
|
from erpnext import get_default_currency
|
||||||
@@ -14,6 +15,9 @@ class SalesPerson(NestedSet):
|
|||||||
nsm_parent_field = "parent_sales_person"
|
nsm_parent_field = "parent_sales_person"
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if not self.enabled:
|
||||||
|
self.validate_sales_person()
|
||||||
|
|
||||||
if not self.parent_sales_person:
|
if not self.parent_sales_person:
|
||||||
self.parent_sales_person = get_root_of("Sales Person")
|
self.parent_sales_person = get_root_of("Sales Person")
|
||||||
|
|
||||||
@@ -55,6 +59,25 @@ class SalesPerson(NestedSet):
|
|||||||
super().on_update()
|
super().on_update()
|
||||||
self.validate_one_root()
|
self.validate_one_root()
|
||||||
|
|
||||||
|
def validate_sales_person(self):
|
||||||
|
sales_team = frappe.qb.DocType("Sales Team")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(sales_team)
|
||||||
|
.select(sales_team.sales_person)
|
||||||
|
.where((sales_team.sales_person == self.name) & (sales_team.parenttype == "Customer"))
|
||||||
|
.groupby(sales_team.sales_person)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
frappe.throw(
|
||||||
|
_("The Sales Person is linked with {0}").format(
|
||||||
|
frappe.bold(
|
||||||
|
f"""<a href="{get_url_to_list("Customer")}?sales_person={self.name}">{"Customers"}</a>"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_email_id(self):
|
def get_email_id(self):
|
||||||
if self.employee:
|
if self.employee:
|
||||||
user = frappe.db.get_value("Employee", self.employee, "user_id")
|
user = frappe.db.get_value("Employee", self.employee, "user_id")
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ from erpnext.stock.get_item_details import get_conversion_factor
|
|||||||
class PickList(Document):
|
class PickList(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_for_qty()
|
self.validate_for_qty()
|
||||||
if self.pick_manually and self.get("locations"):
|
self.validate_stock_qty()
|
||||||
self.validate_stock_qty()
|
self.check_serial_no_status()
|
||||||
self.check_serial_no_status()
|
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
self.update_status()
|
self.update_status()
|
||||||
@@ -42,14 +41,24 @@ class PickList(Document):
|
|||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
|
|
||||||
for row in self.get("locations"):
|
for row in self.get("locations"):
|
||||||
if row.batch_no and not row.qty:
|
if not row.picked_qty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if row.batch_no and row.picked_qty:
|
||||||
batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code)
|
batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code)
|
||||||
|
|
||||||
if row.qty > batch_qty:
|
if row.picked_qty > batch_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}."
|
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}. Please restock the item."
|
||||||
).format(row.idx, row.item_code, batch_qty, row.batch_no, bold(row.warehouse)),
|
).format(
|
||||||
|
row.idx,
|
||||||
|
row.picked_qty,
|
||||||
|
row.item_code,
|
||||||
|
batch_qty,
|
||||||
|
row.batch_no,
|
||||||
|
bold(row.warehouse),
|
||||||
|
),
|
||||||
title=_("Insufficient Stock"),
|
title=_("Insufficient Stock"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,11 +70,11 @@ class PickList(Document):
|
|||||||
"actual_qty",
|
"actual_qty",
|
||||||
)
|
)
|
||||||
|
|
||||||
if row.qty > flt(bin_qty):
|
if row.picked_qty > flt(bin_qty):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}."
|
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}."
|
||||||
).format(row.idx, row.qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
|
).format(row.idx, row.picked_qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
|
||||||
title=_("Insufficient Stock"),
|
title=_("Insufficient Stock"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -253,7 +262,14 @@ class PickList(Document):
|
|||||||
locations_replica = self.get("locations")
|
locations_replica = self.get("locations")
|
||||||
|
|
||||||
# reset
|
# reset
|
||||||
self.delete_key("locations")
|
reset_rows = []
|
||||||
|
for row in self.get("locations"):
|
||||||
|
if not row.picked_qty:
|
||||||
|
reset_rows.append(row)
|
||||||
|
|
||||||
|
for row in reset_rows:
|
||||||
|
self.remove(row)
|
||||||
|
|
||||||
updated_locations = frappe._dict()
|
updated_locations = frappe._dict()
|
||||||
for item_doc in items:
|
for item_doc in items:
|
||||||
item_code = item_doc.item_code
|
item_code = item_doc.item_code
|
||||||
@@ -323,6 +339,9 @@ class PickList(Document):
|
|||||||
# aggregate qty for same item
|
# aggregate qty for same item
|
||||||
item_map = OrderedDict()
|
item_map = OrderedDict()
|
||||||
for item in locations:
|
for item in locations:
|
||||||
|
if item.picked_qty:
|
||||||
|
continue
|
||||||
|
|
||||||
if not item.item_code:
|
if not item.item_code:
|
||||||
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
|
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
|
||||||
if not cint(
|
if not cint(
|
||||||
|
|||||||
@@ -842,5 +842,47 @@ class TestPickList(FrappeTestCase):
|
|||||||
|
|
||||||
for row in pl.locations:
|
for row in pl.locations:
|
||||||
row.qty = row.qty + 10
|
row.qty = row.qty + 10
|
||||||
|
row.picked_qty = row.qty
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, pl.save)
|
self.assertRaises(frappe.ValidationError, pl.save)
|
||||||
|
|
||||||
|
def test_pick_list_not_reset_batch(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item(
|
||||||
|
"Test Do Not Reset Picked Item",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BTH-PICKLT-.######",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
|
||||||
|
se.reload()
|
||||||
|
batch1 = se.items[0].batch_no
|
||||||
|
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
|
||||||
|
se.reload()
|
||||||
|
batch2 = se.items[0].batch_no
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=item, qty=10, rate=100)
|
||||||
|
|
||||||
|
pl = create_pick_list(so.name)
|
||||||
|
pl.save()
|
||||||
|
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.batch_no, batch1)
|
||||||
|
loc.batch_no = batch2
|
||||||
|
loc.picked_qty = 0.0
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.batch_no, batch1)
|
||||||
|
loc.batch_no = batch2
|
||||||
|
loc.picked_qty = 10.0
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.batch_no, batch2)
|
||||||
|
|||||||
Reference in New Issue
Block a user