mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
@@ -370,6 +370,8 @@ class SalesInvoice(SellingController):
|
|||||||
if row.billing_amount:
|
if row.billing_amount:
|
||||||
row.billing_amount = -abs(row.billing_amount)
|
row.billing_amount = -abs(row.billing_amount)
|
||||||
|
|
||||||
|
self.validate_update_stock_for_pick_list_reference()
|
||||||
|
self.set_serial_and_batch_bundle_from_pick_list()
|
||||||
self.update_packing_list()
|
self.update_packing_list()
|
||||||
self.set_billing_hours_and_amount()
|
self.set_billing_hours_and_amount()
|
||||||
self.update_timesheet_billing_for_project()
|
self.update_timesheet_billing_for_project()
|
||||||
@@ -389,6 +391,18 @@ class SalesInvoice(SellingController):
|
|||||||
self.validate_subcontracted_sales_order()
|
self.validate_subcontracted_sales_order()
|
||||||
self.validate_scio_self_rm_qty()
|
self.validate_scio_self_rm_qty()
|
||||||
|
|
||||||
|
def validate_update_stock_for_pick_list_reference(self):
|
||||||
|
if self.update_stock or self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in self.items:
|
||||||
|
if row.get("against_pick_list"):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row {0}: Update Stock must be checked for item {1} because it is against Pick List {2}."
|
||||||
|
).format(row.idx, frappe.bold(row.item_code), frappe.bold(row.against_pick_list))
|
||||||
|
)
|
||||||
|
|
||||||
def validate_accounts(self):
|
def validate_accounts(self):
|
||||||
self.validate_write_off_account()
|
self.validate_write_off_account()
|
||||||
self.validate_account_for_change_amount()
|
self.validate_account_for_change_amount()
|
||||||
@@ -491,6 +505,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
self.update_pick_list_status()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||||
@@ -614,6 +629,7 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_reservation_entries()
|
self.update_stock_reservation_entries()
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
self.update_pick_list_status()
|
||||||
|
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
|
|
||||||
@@ -665,7 +681,8 @@ class SalesInvoice(SellingController):
|
|||||||
if not cint(self.update_stock):
|
if not cint(self.update_stock):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.status_updater.append(
|
self.status_updater.extend(
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"source_dt": "Sales Invoice Item",
|
"source_dt": "Sales Invoice Item",
|
||||||
"target_dt": "Sales Order Item",
|
"target_dt": "Sales Order Item",
|
||||||
@@ -684,7 +701,21 @@ class SalesInvoice(SellingController):
|
|||||||
"overflow_type": "delivery",
|
"overflow_type": "delivery",
|
||||||
"extra_cond": """ and exists(select name from `tabSales Invoice`
|
"extra_cond": """ and exists(select name from `tabSales Invoice`
|
||||||
where name=`tabSales Invoice Item`.parent and update_stock = 1)""",
|
where name=`tabSales Invoice Item`.parent and update_stock = 1)""",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"source_dt": "Sales Invoice Item",
|
||||||
|
"target_dt": "Pick List Item",
|
||||||
|
"join_field": "pick_list_item",
|
||||||
|
"target_field": "delivered_qty",
|
||||||
|
"target_parent_dt": "Pick List",
|
||||||
|
"target_parent_field": "per_delivered",
|
||||||
|
"target_ref_field": "picked_qty",
|
||||||
|
"source_field": "stock_qty",
|
||||||
|
"percent_join_field": "against_pick_list",
|
||||||
|
"status_field": "delivery_status",
|
||||||
|
"keyword": "Delivered",
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not cint(self.is_return):
|
if not cint(self.is_return):
|
||||||
|
|||||||
@@ -104,6 +104,7 @@
|
|||||||
"sales_order",
|
"sales_order",
|
||||||
"so_detail",
|
"so_detail",
|
||||||
"sales_invoice_item",
|
"sales_invoice_item",
|
||||||
|
"pick_list_item",
|
||||||
"column_break_74",
|
"column_break_74",
|
||||||
"delivery_note",
|
"delivery_note",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
"pos_invoice",
|
"pos_invoice",
|
||||||
"pos_invoice_item",
|
"pos_invoice_item",
|
||||||
"scio_detail",
|
"scio_detail",
|
||||||
|
"against_pick_list",
|
||||||
"internal_transfer_section",
|
"internal_transfer_section",
|
||||||
"purchase_order",
|
"purchase_order",
|
||||||
"column_break_92",
|
"column_break_92",
|
||||||
@@ -855,8 +857,8 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate of Stock UOM",
|
"label": "Rate of Stock UOM",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1011,13 +1013,30 @@
|
|||||||
"label": "Consider for Tax Withholding",
|
"label": "Consider for Tax Withholding",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_pick_list",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Against Pick List",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Pick List",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pick_list_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Pick List Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-05-29 12:23:28.259905",
|
"modified": "2026-06-03 13:17:36.145788",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class SalesInvoiceItem(Document):
|
|||||||
|
|
||||||
actual_batch_qty: DF.Float
|
actual_batch_qty: DF.Float
|
||||||
actual_qty: DF.Float
|
actual_qty: DF.Float
|
||||||
|
against_pick_list: DF.Link | None
|
||||||
allow_zero_valuation_rate: DF.Check
|
allow_zero_valuation_rate: DF.Check
|
||||||
amount: DF.Currency
|
amount: DF.Currency
|
||||||
apply_tds: DF.Check
|
apply_tds: DF.Check
|
||||||
@@ -72,6 +73,7 @@ class SalesInvoiceItem(Document):
|
|||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
pick_list_item: DF.Data | None
|
||||||
pos_invoice: DF.Link | None
|
pos_invoice: DF.Link | None
|
||||||
pos_invoice_item: DF.Data | None
|
pos_invoice_item: DF.Data | None
|
||||||
price_list_rate: DF.Currency
|
price_list_rate: DF.Currency
|
||||||
|
|||||||
@@ -1060,6 +1060,44 @@ class SellingController(StockController):
|
|||||||
|
|
||||||
qty_to_undelivered -= qty_can_be_undelivered
|
qty_to_undelivered -= qty_can_be_undelivered
|
||||||
|
|
||||||
|
def set_serial_and_batch_bundle_from_pick_list(self):
|
||||||
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
if item.use_serial_batch_fields or not item.against_pick_list or not self.get("update_stock", 1):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.pick_list_item and not item.serial_and_batch_bundle:
|
||||||
|
filters = {
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"voucher_type": "Pick List",
|
||||||
|
"voucher_no": item.against_pick_list,
|
||||||
|
"voucher_detail_no": item.pick_list_item,
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name")
|
||||||
|
|
||||||
|
if bundle_id:
|
||||||
|
cls_obj = SerialBatchCreation(
|
||||||
|
{
|
||||||
|
"type_of_transaction": "Outward",
|
||||||
|
"serial_and_batch_bundle": bundle_id,
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
"warehouse": item.get("warehouse"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls_obj.duplicate_package()
|
||||||
|
|
||||||
|
item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
||||||
|
|
||||||
|
def update_pick_list_status(self):
|
||||||
|
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
||||||
|
|
||||||
|
pick_lists = {row.against_pick_list for row in self.items if row.against_pick_list}
|
||||||
|
for pick_list in pick_lists:
|
||||||
|
update_pick_list_status(pick_list)
|
||||||
|
|
||||||
|
|
||||||
def set_default_income_account_for_item(obj):
|
def set_default_income_account_for_item(obj):
|
||||||
"""Set income account as default for items in the transaction.
|
"""Set income account as default for items in the transaction.
|
||||||
|
|||||||
@@ -1466,7 +1466,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False, a
|
|||||||
if is_unit_price_row(doc)
|
if is_unit_price_row(doc)
|
||||||
else (doc.qty and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)))
|
else (doc.qty and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)))
|
||||||
)
|
)
|
||||||
and select_item(doc),
|
and select_item(doc)
|
||||||
|
and not args.get("skip_item_mapping"),
|
||||||
},
|
},
|
||||||
"Sales Taxes and Charges": {
|
"Sales Taxes and Charges": {
|
||||||
"doctype": "Sales Taxes and Charges",
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
|||||||
@@ -356,37 +356,6 @@ class DeliveryNote(SellingController):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_serial_and_batch_bundle_from_pick_list(self):
|
|
||||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
|
||||||
|
|
||||||
for item in self.items:
|
|
||||||
if item.use_serial_batch_fields or not item.against_pick_list:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if item.pick_list_item and not item.serial_and_batch_bundle:
|
|
||||||
filters = {
|
|
||||||
"item_code": item.item_code,
|
|
||||||
"voucher_type": "Pick List",
|
|
||||||
"voucher_no": item.against_pick_list,
|
|
||||||
"voucher_detail_no": item.pick_list_item,
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name")
|
|
||||||
|
|
||||||
if bundle_id:
|
|
||||||
cls_obj = SerialBatchCreation(
|
|
||||||
{
|
|
||||||
"type_of_transaction": "Outward",
|
|
||||||
"serial_and_batch_bundle": bundle_id,
|
|
||||||
"item_code": item.get("item_code"),
|
|
||||||
"warehouse": item.get("warehouse"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
cls_obj.duplicate_package()
|
|
||||||
|
|
||||||
item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
|
||||||
|
|
||||||
def validate_references(self):
|
def validate_references(self):
|
||||||
self.validate_sales_order_references()
|
self.validate_sales_order_references()
|
||||||
self.validate_sales_invoice_references()
|
self.validate_sales_invoice_references()
|
||||||
@@ -617,13 +586,6 @@ class DeliveryNote(SellingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_pick_list_status(self):
|
|
||||||
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
|
||||||
|
|
||||||
pick_lists = {row.against_pick_list for row in self.items if row.against_pick_list}
|
|
||||||
for pick_list in pick_lists:
|
|
||||||
update_pick_list_status(pick_list)
|
|
||||||
|
|
||||||
def check_next_docstatus(self):
|
def check_next_docstatus(self):
|
||||||
submit_rv = frappe.db.sql(
|
submit_rv = frappe.db.sql(
|
||||||
"""select t1.name
|
"""select t1.name
|
||||||
|
|||||||
@@ -135,7 +135,12 @@ frappe.ui.form.on("Pick List", {
|
|||||||
if (frm.doc.purpose === "Delivery") {
|
if (frm.doc.purpose === "Delivery") {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Delivery Note"),
|
__("Delivery Note"),
|
||||||
() => frm.trigger("create_delivery_note"),
|
() => frm.events.create_delivery(frm, "Delivery Note"),
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
|
frm.add_custom_button(
|
||||||
|
__("Sales Invoice"),
|
||||||
|
() => frm.events.create_delivery(frm, "Sales Invoice"),
|
||||||
__("Create")
|
__("Create")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -232,9 +237,12 @@ frappe.ui.form.on("Pick List", {
|
|||||||
frm.clear_table("locations");
|
frm.clear_table("locations");
|
||||||
frm.trigger("add_get_items_button");
|
frm.trigger("add_get_items_button");
|
||||||
},
|
},
|
||||||
create_delivery_note: (frm) => {
|
create_delivery(frm, doctype) {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.stock.doctype.pick_list.pick_list.create_delivery_note",
|
method: "erpnext.stock.doctype.pick_list.pick_list.create_delivery",
|
||||||
|
args: {
|
||||||
|
target: doctype,
|
||||||
|
},
|
||||||
frm: frm,
|
frm: frm,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ from frappe.utils.nestedset import get_descendants_of
|
|||||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||||
make_delivery_note as create_delivery_note_from_sales_order,
|
make_delivery_note as create_delivery_note_from_sales_order,
|
||||||
)
|
)
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||||
|
make_sales_invoice as create_sales_invoice_from_sales_order,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
get_auto_batch_nos,
|
get_auto_batch_nos,
|
||||||
)
|
)
|
||||||
@@ -1284,11 +1287,17 @@ def get_available_item_locations_for_other_item(
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_delivery_note(source_name, target_doc=None):
|
def create_delivery_note(source_name, target_doc=None):
|
||||||
|
return create_delivery(source_name, target_doc, "Delivery Note")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_delivery(source_name, target_doc=None, target=None):
|
||||||
pick_list = frappe.get_doc("Pick List", source_name)
|
pick_list = frappe.get_doc("Pick List", source_name)
|
||||||
|
target = target or (frappe.flags.args or {}).get("target") or "Delivery Note"
|
||||||
validate_item_locations(pick_list)
|
validate_item_locations(pick_list)
|
||||||
sales_dict = dict()
|
sales_dict = dict()
|
||||||
sales_orders = []
|
sales_orders = []
|
||||||
delivery_notes = []
|
documents = []
|
||||||
for location in pick_list.locations:
|
for location in pick_list.locations:
|
||||||
if location.sales_order:
|
if location.sales_order:
|
||||||
sales_orders.append(
|
sales_orders.append(
|
||||||
@@ -1318,39 +1327,45 @@ def create_delivery_note(source_name, target_doc=None):
|
|||||||
sales_dict[key] = {row.sales_order for row in rows}
|
sales_dict[key] = {row.sales_order for row in rows}
|
||||||
|
|
||||||
if sales_dict:
|
if sales_dict:
|
||||||
delivery_notes.extend(create_dn_with_so(sales_dict, pick_list))
|
documents.extend(create_delivery_with_so(sales_dict, pick_list, target))
|
||||||
|
|
||||||
if not all(item.sales_order for item in pick_list.locations):
|
if not all(item.sales_order for item in pick_list.locations):
|
||||||
delivery_notes.append(create_dn_wo_so(pick_list))
|
documents.append(create_delivery_wo_so(pick_list, target, target_doc))
|
||||||
|
|
||||||
if len(delivery_notes) == 1:
|
if len(documents) == 1:
|
||||||
return delivery_notes[0]
|
return documents[0]
|
||||||
else:
|
else:
|
||||||
from frappe.utils import comma_and
|
from frappe.utils import comma_and
|
||||||
|
|
||||||
doc_list = [get_link_to_form("Delivery Note", p.name) for p in delivery_notes]
|
doc_list = [get_link_to_form(target, p.name) for p in documents]
|
||||||
frappe.msgprint(_("{0} created").format(comma_and(doc_list)))
|
frappe.msgprint(_("{0} created").format(comma_and(doc_list)))
|
||||||
|
|
||||||
|
|
||||||
def create_dn_wo_so(pick_list, delivery_note=None):
|
def create_dn_wo_so(pick_list, delivery_note=None):
|
||||||
if not delivery_note:
|
return create_delivery_wo_so(pick_list, "Delivery Note", delivery_note)
|
||||||
delivery_note = frappe.new_doc("Delivery Note")
|
|
||||||
|
|
||||||
delivery_note.company = pick_list.company
|
|
||||||
|
def create_delivery_wo_so(pick_list, target, target_doc=None):
|
||||||
|
if not target_doc:
|
||||||
|
target_doc = frappe.new_doc(target)
|
||||||
|
|
||||||
|
target_doc.company = pick_list.company
|
||||||
|
|
||||||
item_table_mapper_without_so = {
|
item_table_mapper_without_so = {
|
||||||
"doctype": "Delivery Note Item",
|
"doctype": f"{target} Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"rate": "rate",
|
"rate": "rate",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"parent": "",
|
"parent": "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
map_pl_locations(pick_list, item_table_mapper_without_so, target_doc)
|
||||||
delivery_note.flags.ignore_mandatory = True
|
target_doc.flags.ignore_mandatory = True
|
||||||
delivery_note.save()
|
if target == "Sales Invoice":
|
||||||
|
target_doc.update_stock = 1
|
||||||
|
target_doc.save()
|
||||||
|
|
||||||
return delivery_note
|
return target_doc
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -1379,36 +1394,53 @@ def create_dn_for_pick_lists(source_name, target_doc=None, kwargs=None):
|
|||||||
pluck="name",
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
delivery_note = create_dn_from_so(pick_list, sales_orders, delivery_note=target_doc, kwargs=kwargs)
|
delivery_note = create_delivery_from_so(
|
||||||
|
pick_list, sales_orders, "Delivery Note", target_doc=target_doc, kwargs=kwargs
|
||||||
|
)
|
||||||
|
|
||||||
if not sales_order_arg and not all(item.sales_order for item in pick_list.locations):
|
if not sales_order_arg and not all(item.sales_order for item in pick_list.locations):
|
||||||
if isinstance(delivery_note, str):
|
if isinstance(delivery_note, str):
|
||||||
delivery_note = frappe.get_doc(frappe.parse_json(delivery_note))
|
delivery_note = frappe.get_doc(frappe.parse_json(delivery_note))
|
||||||
|
|
||||||
delivery_note = create_dn_wo_so(pick_list, delivery_note)
|
delivery_note = create_delivery_wo_so(pick_list, "Delivery Note", delivery_note)
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
def create_dn_with_so(sales_dict, pick_list):
|
def create_dn_with_so(sales_dict, pick_list):
|
||||||
"""Create Delivery Note for each customer (based on SO) in a Pick List."""
|
return create_delivery_with_so(sales_dict, pick_list, "Delivery Note")
|
||||||
delivery_notes = []
|
|
||||||
|
|
||||||
|
def create_delivery_with_so(sales_dict, pick_list, target):
|
||||||
|
"""Create target document for each customer (based on SO) in a Pick List."""
|
||||||
|
documents = []
|
||||||
|
|
||||||
for key in sales_dict:
|
for key in sales_dict:
|
||||||
delivery_note = create_dn_from_so(pick_list, sales_dict[key], None)
|
document = create_delivery_from_so(pick_list, sales_dict[key], target)
|
||||||
if delivery_note:
|
if document:
|
||||||
delivery_note.flags.ignore_mandatory = True
|
document.flags.ignore_mandatory = True
|
||||||
# updates packed_items on save
|
# updates packed_items on save
|
||||||
# save as multiple customers are possible
|
# save as multiple customers are possible
|
||||||
delivery_note.save()
|
if target == "Sales Invoice":
|
||||||
delivery_notes.append(delivery_note)
|
document.update_stock = 1
|
||||||
|
document.save()
|
||||||
|
documents.append(document)
|
||||||
|
|
||||||
return delivery_notes
|
return documents
|
||||||
|
|
||||||
|
|
||||||
def create_dn_from_so(pick_list, sales_order_list, delivery_note=None, kwargs=None):
|
def create_dn_from_so(pick_list, sales_order_list, delivery_note=None, kwargs=None):
|
||||||
|
return create_delivery_from_so(
|
||||||
|
pick_list, sales_order_list, "Delivery Note", target_doc=delivery_note, kwargs=kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_delivery_from_so(pick_list, sales_order_list, target, target_doc=None, kwargs=None):
|
||||||
if not sales_order_list:
|
if not sales_order_list:
|
||||||
return delivery_note
|
return target_doc
|
||||||
|
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
def select_item(d):
|
def select_item(d):
|
||||||
filtered_items = kwargs.get("filtered_children", [])
|
filtered_items = kwargs.get("filtered_children", [])
|
||||||
@@ -1416,11 +1448,11 @@ def create_dn_from_so(pick_list, sales_order_list, delivery_note=None, kwargs=No
|
|||||||
return child_filter
|
return child_filter
|
||||||
|
|
||||||
item_table_mapper = {
|
item_table_mapper = {
|
||||||
"doctype": "Delivery Note Item",
|
"doctype": f"{target} Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"rate": "rate",
|
"rate": "rate",
|
||||||
"name": "so_detail",
|
"name": "so_detail",
|
||||||
"parent": "against_sales_order",
|
"parent": "against_sales_order" if target == "Delivery Note" else "sales_order",
|
||||||
},
|
},
|
||||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
|
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
|
||||||
and doc.delivered_by_supplier != 1
|
and doc.delivered_by_supplier != 1
|
||||||
@@ -1429,20 +1461,22 @@ def create_dn_from_so(pick_list, sales_order_list, delivery_note=None, kwargs=No
|
|||||||
|
|
||||||
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}
|
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}
|
||||||
|
|
||||||
delivery_note = create_delivery_note_from_sales_order(
|
target_doc = (
|
||||||
next(iter(sales_order_list)), delivery_note, kwargs=kwargs
|
create_delivery_note_from_sales_order(next(iter(sales_order_list)), target_doc, kwargs=kwargs)
|
||||||
|
if target == "Delivery Note"
|
||||||
|
else create_sales_invoice_from_sales_order(next(iter(sales_order_list)), target_doc, args=kwargs)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not delivery_note:
|
if not target_doc:
|
||||||
return
|
return
|
||||||
|
|
||||||
for so in sales_order_list:
|
for so in sales_order_list:
|
||||||
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
map_pl_locations(pick_list, item_table_mapper, target_doc, so)
|
||||||
|
|
||||||
return delivery_note
|
return target_doc
|
||||||
|
|
||||||
|
|
||||||
def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
def map_pl_locations(pick_list, item_mapper, target_doc, sales_order=None):
|
||||||
for location in pick_list.locations:
|
for location in pick_list.locations:
|
||||||
if location.sales_order != sales_order or location.product_bundle_item:
|
if location.sales_order != sales_order or location.product_bundle_item:
|
||||||
continue
|
continue
|
||||||
@@ -1454,36 +1488,44 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
|||||||
|
|
||||||
source_doc = sales_order_item or location
|
source_doc = sales_order_item or location
|
||||||
|
|
||||||
dn_item = map_child_doc(source_doc, delivery_note, item_mapper)
|
child_item = map_child_doc(source_doc, target_doc, item_mapper)
|
||||||
|
|
||||||
if dn_item:
|
if child_item:
|
||||||
dn_item.against_pick_list = pick_list.name
|
child_item.against_pick_list = pick_list.name
|
||||||
dn_item.pick_list_item = location.name
|
child_item.pick_list_item = location.name
|
||||||
dn_item.warehouse = location.warehouse
|
child_item.warehouse = location.warehouse
|
||||||
dn_item.qty = flt(location.picked_qty - location.delivered_qty) / (
|
child_item.qty = flt(location.picked_qty - location.delivered_qty) / (
|
||||||
flt(dn_item.conversion_factor) or 1
|
flt(child_item.conversion_factor) or 1
|
||||||
)
|
)
|
||||||
dn_item.batch_no = location.batch_no
|
child_item.batch_no = location.batch_no
|
||||||
dn_item.serial_no = location.serial_no
|
child_item.serial_no = location.serial_no
|
||||||
dn_item.use_serial_batch_fields = location.use_serial_batch_fields
|
child_item.use_serial_batch_fields = location.use_serial_batch_fields
|
||||||
|
|
||||||
update_delivery_note_item(source_doc, dn_item, delivery_note)
|
if not child_item.qty:
|
||||||
|
target_doc.items.remove(child_item)
|
||||||
|
continue
|
||||||
|
|
||||||
add_product_bundles_to_delivery_note(pick_list, delivery_note, item_mapper, sales_order)
|
update_child_item(source_doc, child_item, target_doc)
|
||||||
set_delivery_note_missing_values(delivery_note)
|
|
||||||
|
|
||||||
delivery_note.company = pick_list.company
|
add_product_bundles_to_target(pick_list, target_doc, item_mapper, sales_order)
|
||||||
|
set_target_missing_values(target_doc)
|
||||||
|
|
||||||
|
target_doc.company = pick_list.company
|
||||||
if sales_order:
|
if sales_order:
|
||||||
delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
|
target_doc.customer = frappe.get_value("Sales Order", sales_order, "customer")
|
||||||
|
|
||||||
|
|
||||||
def add_product_bundles_to_delivery_note(
|
def add_product_bundles_to_delivery_note(
|
||||||
pick_list: "PickList", delivery_note, item_mapper, sales_order=None
|
pick_list: "PickList", delivery_note, item_mapper, sales_order=None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add product bundles found in pick list to delivery note.
|
return add_product_bundles_to_target(pick_list, delivery_note, item_mapper, sales_order)
|
||||||
|
|
||||||
|
|
||||||
|
def add_product_bundles_to_target(pick_list, target_doc, item_mapper, sales_order=None) -> None:
|
||||||
|
"""Add product bundles found in pick list to target document.
|
||||||
|
|
||||||
When mapping pick list items, the bundle item itself isn't part of the
|
When mapping pick list items, the bundle item itself isn't part of the
|
||||||
locations. Dynamically fetch and add parent bundle item into DN."""
|
locations. Dynamically fetch and add parent bundle item into target document."""
|
||||||
product_bundles = pick_list._get_product_bundles()
|
product_bundles = pick_list._get_product_bundles()
|
||||||
product_bundle_qty_map = pick_list._get_product_bundle_qty_map(product_bundles.values())
|
product_bundle_qty_map = pick_list._get_product_bundle_qty_map(product_bundles.values())
|
||||||
|
|
||||||
@@ -1492,13 +1534,13 @@ def add_product_bundles_to_delivery_note(
|
|||||||
if sales_order and sales_order_item.parent != sales_order:
|
if sales_order and sales_order_item.parent != sales_order:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dn_bundle_item = map_child_doc(sales_order_item, delivery_note, item_mapper)
|
target_bundle_item = map_child_doc(sales_order_item, target_doc, item_mapper)
|
||||||
dn_bundle_item.qty = pick_list._compute_picked_qty_for_bundle(
|
target_bundle_item.qty = pick_list._compute_picked_qty_for_bundle(
|
||||||
so_row, product_bundle_qty_map[value.item_code]
|
so_row, product_bundle_qty_map[value.item_code]
|
||||||
)
|
)
|
||||||
dn_bundle_item.pick_list_item = value.pick_list_item
|
target_bundle_item.pick_list_item = value.pick_list_item
|
||||||
dn_bundle_item.against_pick_list = pick_list.name
|
target_bundle_item.against_pick_list = pick_list.name
|
||||||
update_delivery_note_item(sales_order_item, dn_bundle_item, delivery_note)
|
update_child_item(sales_order_item, target_bundle_item, target_doc)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -1572,12 +1614,16 @@ def get_actual_qty(item_code, warehouse):
|
|||||||
|
|
||||||
|
|
||||||
def update_delivery_note_item(source, target, delivery_note):
|
def update_delivery_note_item(source, target, delivery_note):
|
||||||
cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
|
return update_child_item(source, target, delivery_note)
|
||||||
|
|
||||||
|
|
||||||
|
def update_child_item(source, target, target_doc):
|
||||||
|
cost_center = frappe.db.get_value("Project", target_doc.project, "cost_center")
|
||||||
if not cost_center:
|
if not cost_center:
|
||||||
cost_center = get_cost_center(source.item_code, "Item", delivery_note.company)
|
cost_center = get_cost_center(source.item_code, "Item", target_doc.company)
|
||||||
|
|
||||||
if not cost_center:
|
if not cost_center:
|
||||||
cost_center = get_cost_center(source.item_group, "Item Group", delivery_note.company)
|
cost_center = get_cost_center(source.item_group, "Item Group", target_doc.company)
|
||||||
|
|
||||||
target.cost_center = cost_center
|
target.cost_center = cost_center
|
||||||
|
|
||||||
@@ -1592,6 +1638,10 @@ def get_cost_center(for_item, from_doctype, company):
|
|||||||
|
|
||||||
|
|
||||||
def set_delivery_note_missing_values(target):
|
def set_delivery_note_missing_values(target):
|
||||||
|
return set_target_missing_values(target)
|
||||||
|
|
||||||
|
|
||||||
|
def set_target_missing_values(target):
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("set_po_nos")
|
target.run_method("set_po_nos")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ def get_data():
|
|||||||
"non_standard_fieldnames": {
|
"non_standard_fieldnames": {
|
||||||
"Stock Reservation Entry": "from_voucher_no",
|
"Stock Reservation Entry": "from_voucher_no",
|
||||||
"Delivery Note": "against_pick_list",
|
"Delivery Note": "against_pick_list",
|
||||||
|
"Sales Invoice": "against_pick_list",
|
||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Sales Order": ["locations", "sales_order"],
|
"Sales Order": ["locations", "sales_order"],
|
||||||
@@ -14,7 +15,7 @@ def get_data():
|
|||||||
"transactions": [
|
"transactions": [
|
||||||
{
|
{
|
||||||
"label": _("Sales"),
|
"label": _("Sales"),
|
||||||
"items": ["Sales Order", "Delivery Note"],
|
"items": ["Sales Order", "Delivery Note", "Sales Invoice"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Manufacturing"),
|
"label": _("Manufacturing"),
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ from erpnext.selling.doctype.sales_order.sales_order import create_pick_list
|
|||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||||
from erpnext.stock.doctype.packed_item.test_packed_item import create_product_bundle
|
from erpnext.stock.doctype.packed_item.test_packed_item import create_product_bundle
|
||||||
from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note, create_dn_for_pick_lists
|
from erpnext.stock.doctype.pick_list.pick_list import (
|
||||||
|
create_delivery,
|
||||||
|
create_delivery_note,
|
||||||
|
create_dn_for_pick_lists,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||||
get_batch_from_bundle,
|
get_batch_from_bundle,
|
||||||
@@ -768,6 +772,198 @@ class TestPickList(ERPNextTestSuite):
|
|||||||
if dn_item.item_code == "_Test Item 2":
|
if dn_item.item_code == "_Test Item 2":
|
||||||
self.assertEqual(dn_item.qty, 2)
|
self.assertEqual(dn_item.qty, 2)
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings("Stock Settings", {"use_serial_batch_fields": 1})
|
||||||
|
def test_sales_invoice_from_pick_list_copies_old_batch_serial_fields(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item(
|
||||||
|
f"_Test PLSI Old Fields {frappe.generate_hash(length=8)}",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"batch_number_series": f"PLSI-OLD-B-{frappe.generate_hash(length=6)}-.#####",
|
||||||
|
"serial_no_series": f"PLSI-OLD-S-{frappe.generate_hash(length=6)}-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry(item=item, to_warehouse=warehouse, qty=2, basic_rate=100)
|
||||||
|
sales_order = make_sales_order(item_code=item, warehouse=warehouse, qty=2, rate=100)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.submit()
|
||||||
|
pick_list_item = pick_list.locations[0]
|
||||||
|
|
||||||
|
self.assertTrue(pick_list_item.use_serial_batch_fields)
|
||||||
|
self.assertTrue(pick_list_item.batch_no)
|
||||||
|
self.assertTrue(pick_list_item.serial_no)
|
||||||
|
|
||||||
|
sales_invoice = create_delivery(pick_list.name, target="Sales Invoice")
|
||||||
|
sales_invoice_item = sales_invoice.items[0]
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice.update_stock, 1)
|
||||||
|
self.assertEqual(sales_invoice_item.against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(sales_invoice_item.pick_list_item, pick_list_item.name)
|
||||||
|
self.assertEqual(sales_invoice_item.use_serial_batch_fields, 1)
|
||||||
|
self.assertEqual(sales_invoice_item.batch_no, pick_list_item.batch_no)
|
||||||
|
self.assertEqual(
|
||||||
|
set(sales_invoice_item.serial_no.split("\n")), set(pick_list_item.serial_no.split("\n"))
|
||||||
|
)
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings("Stock Settings", {"use_serial_batch_fields": 0})
|
||||||
|
def test_sales_invoice_from_pick_list_copies_serial_and_batch_bundle(self):
|
||||||
|
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item(
|
||||||
|
f"_Test PLSI Bundle {frappe.generate_hash(length=8)}",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"batch_number_series": f"PLSI-BND-B-{frappe.generate_hash(length=6)}-.#####",
|
||||||
|
"serial_no_series": f"PLSI-BND-S-{frappe.generate_hash(length=6)}-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(item=item, to_warehouse=warehouse, qty=2, basic_rate=100)
|
||||||
|
batch_no = get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle)
|
||||||
|
serial_nos = get_serial_nos_from_bundle(stock_entry.items[0].serial_and_batch_bundle)
|
||||||
|
sales_order = make_sales_order(item_code=item, warehouse=warehouse, qty=2, rate=100)
|
||||||
|
|
||||||
|
pick_list = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Pick List",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
"items_based_on": "Sales Order",
|
||||||
|
"purpose": "Delivery",
|
||||||
|
"pick_manually": 1,
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"item_code": item,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"qty": 2,
|
||||||
|
"stock_qty": 2,
|
||||||
|
"picked_qty": 2,
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"sales_order": sales_order.name,
|
||||||
|
"sales_order_item": sales_order.items[0].name,
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
|
pick_list.locations[0].serial_and_batch_bundle = (
|
||||||
|
SerialBatchCreation(
|
||||||
|
{
|
||||||
|
"item_code": item,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"voucher_type": "Pick List",
|
||||||
|
"voucher_no": pick_list.name,
|
||||||
|
"voucher_detail_no": pick_list.locations[0].name,
|
||||||
|
"qty": -2,
|
||||||
|
"batches": frappe._dict({batch_no: 2}),
|
||||||
|
"serial_nos": serial_nos,
|
||||||
|
"type_of_transaction": "Outward",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"do_not_submit": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.make_serial_and_batch_bundle()
|
||||||
|
.name
|
||||||
|
)
|
||||||
|
pick_list.locations[0].db_set(
|
||||||
|
{
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
"batch_no": None,
|
||||||
|
"serial_no": None,
|
||||||
|
"serial_and_batch_bundle": pick_list.locations[0].serial_and_batch_bundle,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
pick_list.reload()
|
||||||
|
pick_list_item = pick_list.locations[0]
|
||||||
|
|
||||||
|
self.assertFalse(pick_list_item.use_serial_batch_fields)
|
||||||
|
self.assertTrue(pick_list_item.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
sales_invoice = create_delivery(pick_list.name, target="Sales Invoice")
|
||||||
|
sales_invoice_item = sales_invoice.items[0]
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice_item.against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(sales_invoice_item.pick_list_item, pick_list_item.name)
|
||||||
|
self.assertFalse(sales_invoice_item.use_serial_batch_fields)
|
||||||
|
self.assertTrue(sales_invoice_item.serial_and_batch_bundle)
|
||||||
|
self.assertNotEqual(
|
||||||
|
sales_invoice_item.serial_and_batch_bundle, pick_list_item.serial_and_batch_bundle
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
get_batch_from_bundle(sales_invoice_item.serial_and_batch_bundle),
|
||||||
|
get_batch_from_bundle(pick_list_item.serial_and_batch_bundle),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
set(get_serial_nos_from_bundle(sales_invoice_item.serial_and_batch_bundle)),
|
||||||
|
set(get_serial_nos_from_bundle(pick_list_item.serial_and_batch_bundle)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sales_invoice_from_sales_order_pick_list_updates_sales_order(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item().name
|
||||||
|
|
||||||
|
make_stock_entry(item=item, to_warehouse=warehouse, qty=5, basic_rate=100)
|
||||||
|
sales_order = make_sales_order(item_code=item, warehouse=warehouse, qty=5, rate=100)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.submit()
|
||||||
|
|
||||||
|
sales_invoice = create_delivery(pick_list.name, target="Sales Invoice")
|
||||||
|
sales_invoice_item = sales_invoice.items[0]
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice_item.sales_order, sales_order.name)
|
||||||
|
self.assertEqual(sales_invoice_item.so_detail, sales_order.items[0].name)
|
||||||
|
self.assertEqual(sales_invoice_item.against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(sales_invoice_item.pick_list_item, pick_list.locations[0].name)
|
||||||
|
|
||||||
|
sales_invoice.submit()
|
||||||
|
pick_list.reload()
|
||||||
|
sales_order.reload()
|
||||||
|
|
||||||
|
self.assertEqual(pick_list.locations[0].delivered_qty, pick_list.locations[0].picked_qty)
|
||||||
|
self.assertEqual(pick_list.per_delivered, 100)
|
||||||
|
self.assertEqual(pick_list.delivery_status, "Fully Delivered")
|
||||||
|
self.assertEqual(pick_list.status, "Completed")
|
||||||
|
|
||||||
|
self.assertEqual(sales_order.items[0].picked_qty, 5)
|
||||||
|
self.assertEqual(sales_order.items[0].delivered_qty, 5)
|
||||||
|
self.assertEqual(sales_order.per_delivered, 100)
|
||||||
|
self.assertEqual(sales_order.delivery_status, "Fully Delivered")
|
||||||
|
self.assertEqual(sales_order.per_billed, 100)
|
||||||
|
self.assertEqual(sales_order.billing_status, "Fully Billed")
|
||||||
|
self.assertEqual(sales_order.status, "Completed")
|
||||||
|
|
||||||
|
def test_sales_invoice_against_pick_list_requires_update_stock(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item().name
|
||||||
|
|
||||||
|
make_stock_entry(item=item, to_warehouse=warehouse, qty=5, basic_rate=100)
|
||||||
|
sales_order = make_sales_order(item_code=item, warehouse=warehouse, qty=5, rate=100)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.submit()
|
||||||
|
|
||||||
|
sales_invoice = create_delivery(pick_list.name, target="Sales Invoice")
|
||||||
|
sales_invoice.update_stock = 0
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.ValidationError,
|
||||||
|
"Update Stock.*Pick List",
|
||||||
|
sales_invoice.save,
|
||||||
|
)
|
||||||
|
|
||||||
def test_picklist_reserved_qty_validation(self):
|
def test_picklist_reserved_qty_validation(self):
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user