refactor(delivery_note): move mapping functions to mapper.py

This commit is contained in:
Nabin Hait
2026-05-29 12:32:57 +05:30
parent 2cf51a0367
commit 8192d70f83
2 changed files with 593 additions and 572 deletions

View File

@@ -2,25 +2,27 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address
from frappe.contacts.doctype.contact.contact import get_default_contact
from frappe.desk.notifications import clear_doctype_notifications
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.query_builder import DocType
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt
from erpnext.accounts.party import get_due_date
from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes
from erpnext.controllers.selling_controller import SellingController
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
from .mapper import (
make_delivery_trip,
make_installation_note,
make_inter_company_purchase_receipt,
make_packing_slip,
make_sales_invoice,
make_sales_return,
make_shipment,
)
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@@ -754,8 +756,6 @@ class DeliveryNote(SellingController):
def update_billed_amount_based_on_so(so_detail, update_modified=True):
from frappe.query_builder.functions import Sum
# Billed against Sales Order directly
si = frappe.qb.DocType("Sales Invoice").as_("si")
si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
@@ -850,569 +850,7 @@ def get_list_context(context=None):
return list_context
def get_invoiced_qty_map(delivery_note):
"""returns a map: {dn_detail: invoiced_qty}"""
sii = DocType("Sales Invoice Item")
invoiced_qty_map = frappe._dict(
(
frappe.qb.from_(sii)
.select(sii.dn_detail, Sum(sii.qty).as_("qty"))
.where((sii.delivery_note == delivery_note) & (sii.docstatus == 1))
.groupby(sii.dn_detail)
).run()
)
return invoiced_qty_map
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
dn = DocType("Delivery Note")
dni = DocType("Delivery Note Item")
returned_qty_map = frappe._dict(
(
frappe.qb.from_(dni)
.join(dn)
.on(dn.name == dni.parent)
.select(dni.dn_detail, Sum(Abs(dni.qty)).as_("qty"))
.where(
(dn.docstatus == 1)
& (dn.is_return == 1)
& (dn.return_against == delivery_note)
& (dni.qty <= 0)
)
.groupby(dni.dn_detail)
).run()
)
return returned_qty_map
@frappe.whitelist()
def make_sales_invoice(
source_name: str, target_doc: str | Document | None = None, args: dict | str | None = None
):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
doc = frappe.get_doc("Delivery Note", source_name)
to_make_invoice_qty_map = {}
returned_qty_map = get_returned_qty_map(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
if len(target.get("items")) == 0:
frappe.throw(_("All these items have already been Invoiced/Returned"))
if args and args.get("merge_taxes"):
merge_taxes(source, target)
target.run_method("calculate_taxes_and_totals")
# set company address
if source.company_address:
target.update({"company_address": source.company_address})
else:
# set company address
target.update(get_company_address(target.company))
if target.company_address:
target.update(get_fetch_values("Sales Invoice", "company_address", target.company_address))
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = to_make_invoice_qty_map[source_doc.name]
target_doc._old_name = source_doc.name
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = 0
if returned_qty_map.get(item_row.name, 0) > 0:
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
returned_qty_map[item_row.name] -= pending_qty
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
returned_qty -= pending_qty
else:
pending_qty -= returned_qty
returned_qty = 0
to_make_invoice_qty_map[item_row.name] = pending_qty
return pending_qty
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Sales Invoice",
"field_map": {"is_return": "is_return"},
"validation": {"docstatus": ["=", 1]},
},
"Delivery Note Item": {
"doctype": "Sales Invoice Item",
"field_map": {
"name": "dn_detail",
"parent": "delivery_note",
"so_detail": "so_detail",
"against_sales_order": "sales_order",
"cost_center": "cost_center",
},
"postprocess": update_item,
"filter": lambda d: get_pending_qty(d) <= 0
if not doc.get("is_return")
else get_pending_qty(d) > 0,
"condition": select_item,
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",
"reset_value": not (args and args.get("merge_taxes")),
"ignore": args.get("merge_taxes") if args else 0,
},
"Sales Team": {
"doctype": "Sales Team",
"field_map": {"incentives": "incentives"},
"add_if_empty": True,
},
},
target_doc,
set_missing_values,
)
automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
if not doc.is_return:
from erpnext.accounts.services.payment_schedule import PaymentScheduleService
ps = PaymentScheduleService(doc)
so, doctype, fieldname = ps.get_order_details()
if (
ps.linked_order_has_payment_terms(so, fieldname, doctype)
and not automatically_fetch_payment_terms
):
payment_terms_template = frappe.db.get_value(doctype, so, "payment_terms_template")
doc.payment_terms_template = payment_terms_template
doc.due_date = get_due_date(
doc.posting_date,
"Customer",
doc.customer,
doc.company,
template_name=doc.payment_terms_template,
)
elif automatically_fetch_payment_terms:
ps.set_payment_schedule()
return doc
@frappe.whitelist()
def make_delivery_trip(
source_name: str, target_doc: str | Document | None = None, kwargs: dict | None = None
):
if not target_doc:
target_doc = frappe.new_doc("Delivery Trip")
def update_address(source_doc, target_doc, source_parent):
target_doc.address = source_doc.shipping_address_name or source_doc.customer_address
target_doc.customer_address = source_doc.shipping_address or source_doc.address_display
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Delivery Stop",
"on_parent": target_doc,
"field_map": {
"name": "delivery_note",
"contact_person": "contact",
"contact_display": "customer_contact",
},
"postprocess": update_address,
},
},
ignore_child_tables=True,
)
return doclist
@frappe.whitelist()
def make_installation_note(
source_name: str, target_doc: str | Document | None = None, kwargs: dict | None = None
):
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.installed_qty)
target.serial_no = obj.serial_no
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {"doctype": "Installation Note", "validation": {"docstatus": ["=", 1]}},
"Delivery Note Item": {
"doctype": "Installation Note Item",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
},
"postprocess": update_item,
"condition": lambda doc: doc.installed_qty < doc.qty,
},
},
target_doc,
)
return doclist
@frappe.whitelist()
def make_packing_slip(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
target.run_method("set_missing_values")
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.packed_qty)
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Packing Slip",
"field_map": {"name": "delivery_note", "letter_head": "letter_head"},
"validation": {"docstatus": ["=", 0]},
},
"Delivery Note Item": {
"doctype": "Packing Slip Item",
"field_map": {
"item_code": "item_code",
"item_name": "item_name",
"batch_no": "batch_no",
"description": "description",
"qty": "qty",
"uom": "stock_uom",
"name": "dn_detail",
},
"postprocess": update_item,
"condition": lambda item: (
not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0})
and flt(item.packed_qty) < flt(item.qty)
),
},
"Packed Item": {
"doctype": "Packing Slip Item",
"field_map": {
"item_code": "item_code",
"item_name": "item_name",
"batch_no": "batch_no",
"description": "description",
"qty": "qty",
"name": "pi_detail",
},
"postprocess": update_item,
"condition": lambda item: (flt(item.packed_qty) < flt(item.qty)),
},
},
target_doc,
set_missing_values,
)
return doclist
@frappe.whitelist()
def make_shipment(source_name: str, target_doc: str | Document | None = None):
def postprocess(source, target):
user = frappe.db.get_value(
"User", frappe.session.user, ["email", "full_name", "phone", "mobile_no"], as_dict=1
)
target.pickup_contact_email = user.email
pickup_contact_display = f"{user.full_name}"
if user:
if user.email:
pickup_contact_display += "<br>" + user.email
if user.phone:
pickup_contact_display += "<br>" + user.phone
if user.mobile_no and not user.phone:
pickup_contact_display += "<br>" + user.mobile_no
target.pickup_contact = pickup_contact_display
# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
target.pickup_contact_person = frappe.session.user
contact_person = source.contact_person or get_default_contact("Customer", source.customer)
if contact_person:
contact = frappe.db.get_value(
"Contact", contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
)
delivery_contact_display = source.contact_display or contact_person or ""
if contact and not source.contact_display:
if contact.email_id:
delivery_contact_display += "<br>" + contact.email_id
if contact.phone:
delivery_contact_display += "<br>" + contact.phone
if contact.mobile_no and not contact.phone:
delivery_contact_display += "<br>" + contact.mobile_no
target.delivery_contact_name = contact_person
if contact and contact.email_id and not target.delivery_contact_email:
target.delivery_contact_email = contact.email_id
target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
target.delivery_address_name = source.shipping_address_name
target.delivery_address = source.shipping_address
elif source.customer_address:
target.delivery_address_name = source.customer_address
target.delivery_address = source.address_display
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Shipment",
"field_map": {
"grand_total": "value_of_goods",
"company": "pickup_company",
"company_address": "pickup_address_name",
"company_address_display": "pickup_address",
"customer": "delivery_customer",
"contact_person": "delivery_contact_name",
"contact_email": "delivery_contact_email",
},
"validation": {"docstatus": ["=", 1]},
},
"Delivery Note Item": {
"doctype": "Shipment Delivery Note",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
"base_amount": "grand_total",
},
},
},
target_doc,
postprocess,
)
return doclist
@frappe.whitelist()
def make_sales_return(source_name: str, target_doc: str | Document | None = None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return make_return_doc("Delivery Note", source_name, target_doc)
@frappe.whitelist()
def update_delivery_note_status(docname: str, status: str):
dn = frappe.get_lazy_doc("Delivery Note", docname)
dn.update_status(status)
@frappe.whitelist()
def make_inter_company_purchase_receipt(source_name: str, target_doc: str | Document | None = None):
return make_inter_company_transaction("Delivery Note", source_name, target_doc)
def make_inter_company_transaction(doctype, source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
get_inter_company_details,
set_purchase_references,
update_address,
update_taxes,
validate_inter_company_transaction,
)
if doctype == "Delivery Note":
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Receipt"
source_document_warehouse_field = "target_warehouse"
target_document_warehouse_field = "from_warehouse"
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Delivery Note"
source_document_warehouse_field = "from_warehouse"
target_document_warehouse_field = "target_warehouse"
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
def set_missing_values(source, target):
target.run_method("set_missing_values")
set_purchase_references(target)
if target.doctype == "Purchase Receipt":
master_doctype = "Purchase Taxes and Charges Template"
else:
master_doctype = "Sales Taxes and Charges Template"
if not target.get("taxes") and target.get("taxes_and_charges"):
for tax in get_taxes_and_charges(master_doctype, target.get("taxes_and_charges")):
target.append("taxes", tax)
if not target.get("items"):
frappe.throw(_("All items have already been received"))
def update_details(source_doc, target_doc, source_parent):
def _validate_address_link(address, link_doctype, link_name):
return frappe.db.get_value(
"Dynamic Link",
{
"parent": address,
"parenttype": "Address",
"link_doctype": link_doctype,
"link_name": link_name,
},
"parent",
)
target_doc.inter_company_invoice_reference = source_doc.name
if target_doc.doctype == "Purchase Receipt":
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.buying_price_list = source_doc.selling_price_list
target_doc.is_internal_supplier = 1
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
if source_doc.company_address and _validate_address_link(
source_doc.company_address, "Supplier", details.get("party")
):
update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
if source_doc.dispatch_address_name and _validate_address_link(
source_doc.dispatch_address_name, "Company", details.get("company")
):
update_address(
target_doc,
"dispatch_address",
"dispatch_address_display",
source_doc.dispatch_address_name,
)
if source_doc.shipping_address_name and _validate_address_link(
source_doc.shipping_address_name, "Company", details.get("company")
):
update_address(
target_doc,
"shipping_address",
"shipping_address_display",
source_doc.shipping_address_name,
)
if source_doc.customer_address and _validate_address_link(
source_doc.customer_address, "Company", details.get("company")
):
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
)
update_taxes(
target_doc,
party=target_doc.supplier,
party_type="Supplier",
company=target_doc.company,
doctype=target_doc.doctype,
party_address=target_doc.supplier_address,
company_address=target_doc.shipping_address,
)
else:
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
target_doc.company_address = source_doc.supplier_address
target_doc.selling_price_list = source_doc.buying_price_list
target_doc.is_internal_customer = 1
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
if source_doc.supplier_address and _validate_address_link(
source_doc.supplier_address, "Company", details.get("company")
):
update_address(
target_doc, "company_address", "company_address_display", source_doc.supplier_address
)
if source_doc.shipping_address and _validate_address_link(
source_doc.shipping_address, "Customer", details.get("party")
):
update_address(
target_doc, "shipping_address_name", "shipping_address", source_doc.shipping_address
)
if source_doc.shipping_address and _validate_address_link(
source_doc.shipping_address, "Customer", details.get("party")
):
update_address(target_doc, "customer_address", "address_display", source_doc.shipping_address)
update_taxes(
target_doc,
party=target_doc.customer,
party_type="Customer",
company=target_doc.company,
doctype=target_doc.doctype,
party_address=target_doc.customer_address,
company_address=target_doc.company_address,
shipping_address_name=target_doc.shipping_address_name,
)
def update_item(source, target, source_parent):
if source_parent.doctype == "Delivery Note" and source.received_qty:
target.qty = flt(source.qty) + flt(source.returned_qty) - flt(source.received_qty)
if source.get("use_serial_batch_fields"):
target.set("use_serial_batch_fields", 1)
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.set("use_serial_batch_fields", 1)
doclist = get_mapped_doc(
doctype,
source_name,
{
doctype: {
"doctype": target_doctype,
"postprocess": update_details,
"field_no_map": ["taxes_and_charges", "set_warehouse"],
"field_map": {"shipping_address_name": "shipping_address"},
},
doctype + " Item": {
"doctype": target_doctype + " Item",
"field_map": {
source_document_warehouse_field: target_document_warehouse_field,
"name": "delivery_note_item",
"purchase_order": "purchase_order",
"purchase_order_item": "purchase_order_item",
"material_request": "material_request",
"Material_request_item": "material_request_item",
},
"field_no_map": ["warehouse"],
"condition": lambda item: item.received_qty < item.qty + item.returned_qty,
"postprocess": update_item,
},
},
target_doc,
set_missing_values,
)
return doclist

View File

@@ -0,0 +1,583 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _
from frappe.contacts.doctype.contact.contact import get_default_contact
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.query_builder import DocType
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import flt
from erpnext.accounts.party import get_due_date
from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes
def get_invoiced_qty_map(delivery_note: str) -> dict:
"""returns a map: {dn_detail: invoiced_qty}"""
sii = DocType("Sales Invoice Item")
invoiced_qty_map = frappe._dict(
(
frappe.qb.from_(sii)
.select(sii.dn_detail, Sum(sii.qty).as_("qty"))
.where((sii.delivery_note == delivery_note) & (sii.docstatus == 1))
.groupby(sii.dn_detail)
).run()
)
return invoiced_qty_map
def get_returned_qty_map(delivery_note: str) -> dict:
"""returns a map: {so_detail: returned_qty}"""
dn = DocType("Delivery Note")
dni = DocType("Delivery Note Item")
returned_qty_map = frappe._dict(
(
frappe.qb.from_(dni)
.join(dn)
.on(dn.name == dni.parent)
.select(dni.dn_detail, Sum(Abs(dni.qty)).as_("qty"))
.where(
(dn.docstatus == 1)
& (dn.is_return == 1)
& (dn.return_against == delivery_note)
& (dni.qty <= 0)
)
.groupby(dni.dn_detail)
).run()
)
return returned_qty_map
@frappe.whitelist()
def make_sales_invoice(
source_name: str, target_doc: str | Document | None = None, args: dict | str | None = None
):
from frappe.contacts.doctype.address.address import get_company_address
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
doc = frappe.get_doc("Delivery Note", source_name)
to_make_invoice_qty_map = {}
returned_qty_map = get_returned_qty_map(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
if len(target.get("items")) == 0:
frappe.throw(_("All these items have already been Invoiced/Returned"))
if args and args.get("merge_taxes"):
merge_taxes(source, target)
target.run_method("calculate_taxes_and_totals")
# set company address
if source.company_address:
target.update({"company_address": source.company_address})
else:
# set company address
target.update(get_company_address(target.company))
if target.company_address:
target.update(get_fetch_values("Sales Invoice", "company_address", target.company_address))
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = to_make_invoice_qty_map[source_doc.name]
target_doc._old_name = source_doc.name
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = 0
if returned_qty_map.get(item_row.name, 0) > 0:
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
returned_qty_map[item_row.name] -= pending_qty
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
returned_qty -= pending_qty
else:
pending_qty -= returned_qty
returned_qty = 0
to_make_invoice_qty_map[item_row.name] = pending_qty
return pending_qty
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Sales Invoice",
"field_map": {"is_return": "is_return"},
"validation": {"docstatus": ["=", 1]},
},
"Delivery Note Item": {
"doctype": "Sales Invoice Item",
"field_map": {
"name": "dn_detail",
"parent": "delivery_note",
"so_detail": "so_detail",
"against_sales_order": "sales_order",
"cost_center": "cost_center",
},
"postprocess": update_item,
"filter": lambda d: get_pending_qty(d) <= 0
if not doc.get("is_return")
else get_pending_qty(d) > 0,
"condition": select_item,
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",
"reset_value": not (args and args.get("merge_taxes")),
"ignore": args.get("merge_taxes") if args else 0,
},
"Sales Team": {
"doctype": "Sales Team",
"field_map": {"incentives": "incentives"},
"add_if_empty": True,
},
},
target_doc,
set_missing_values,
)
from frappe.utils import cint
automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
if not doc.is_return:
from erpnext.accounts.services.payment_schedule import PaymentScheduleService
ps = PaymentScheduleService(doc)
so, doctype, fieldname = ps.get_order_details()
if (
ps.linked_order_has_payment_terms(so, fieldname, doctype)
and not automatically_fetch_payment_terms
):
payment_terms_template = frappe.db.get_value(doctype, so, "payment_terms_template")
doc.payment_terms_template = payment_terms_template
doc.due_date = get_due_date(
doc.posting_date,
"Customer",
doc.customer,
doc.company,
template_name=doc.payment_terms_template,
)
elif automatically_fetch_payment_terms:
ps.set_payment_schedule()
return doc
@frappe.whitelist()
def make_delivery_trip(
source_name: str, target_doc: str | Document | None = None, kwargs: dict | None = None
):
if not target_doc:
target_doc = frappe.new_doc("Delivery Trip")
def update_address(source_doc, target_doc, source_parent):
target_doc.address = source_doc.shipping_address_name or source_doc.customer_address
target_doc.customer_address = source_doc.shipping_address or source_doc.address_display
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Delivery Stop",
"on_parent": target_doc,
"field_map": {
"name": "delivery_note",
"contact_person": "contact",
"contact_display": "customer_contact",
},
"postprocess": update_address,
},
},
ignore_child_tables=True,
)
return doclist
@frappe.whitelist()
def make_installation_note(
source_name: str, target_doc: str | Document | None = None, kwargs: dict | None = None
):
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.installed_qty)
target.serial_no = obj.serial_no
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {"doctype": "Installation Note", "validation": {"docstatus": ["=", 1]}},
"Delivery Note Item": {
"doctype": "Installation Note Item",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
},
"postprocess": update_item,
"condition": lambda doc: doc.installed_qty < doc.qty,
},
},
target_doc,
)
return doclist
@frappe.whitelist()
def make_packing_slip(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
target.run_method("set_missing_values")
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.packed_qty)
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Packing Slip",
"field_map": {"name": "delivery_note", "letter_head": "letter_head"},
"validation": {"docstatus": ["=", 0]},
},
"Delivery Note Item": {
"doctype": "Packing Slip Item",
"field_map": {
"item_code": "item_code",
"item_name": "item_name",
"batch_no": "batch_no",
"description": "description",
"qty": "qty",
"uom": "stock_uom",
"name": "dn_detail",
},
"postprocess": update_item,
"condition": lambda item: (
not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0})
and flt(item.packed_qty) < flt(item.qty)
),
},
"Packed Item": {
"doctype": "Packing Slip Item",
"field_map": {
"item_code": "item_code",
"item_name": "item_name",
"batch_no": "batch_no",
"description": "description",
"qty": "qty",
"name": "pi_detail",
},
"postprocess": update_item,
"condition": lambda item: (flt(item.packed_qty) < flt(item.qty)),
},
},
target_doc,
set_missing_values,
)
return doclist
@frappe.whitelist()
def make_shipment(source_name: str, target_doc: str | Document | None = None):
def postprocess(source, target):
user = frappe.db.get_value(
"User", frappe.session.user, ["email", "full_name", "phone", "mobile_no"], as_dict=1
)
target.pickup_contact_email = user.email
pickup_contact_display = f"{user.full_name}"
if user:
if user.email:
pickup_contact_display += "<br>" + user.email
if user.phone:
pickup_contact_display += "<br>" + user.phone
if user.mobile_no and not user.phone:
pickup_contact_display += "<br>" + user.mobile_no
target.pickup_contact = pickup_contact_display
# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
target.pickup_contact_person = frappe.session.user
contact_person = source.contact_person or get_default_contact("Customer", source.customer)
if contact_person:
contact = frappe.db.get_value(
"Contact", contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
)
delivery_contact_display = source.contact_display or contact_person or ""
if contact and not source.contact_display:
if contact.email_id:
delivery_contact_display += "<br>" + contact.email_id
if contact.phone:
delivery_contact_display += "<br>" + contact.phone
if contact.mobile_no and not contact.phone:
delivery_contact_display += "<br>" + contact.mobile_no
target.delivery_contact_name = contact_person
if contact and contact.email_id and not target.delivery_contact_email:
target.delivery_contact_email = contact.email_id
target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
target.delivery_address_name = source.shipping_address_name
target.delivery_address = source.shipping_address
elif source.customer_address:
target.delivery_address_name = source.customer_address
target.delivery_address = source.address_display
doclist = get_mapped_doc(
"Delivery Note",
source_name,
{
"Delivery Note": {
"doctype": "Shipment",
"field_map": {
"grand_total": "value_of_goods",
"company": "pickup_company",
"company_address": "pickup_address_name",
"company_address_display": "pickup_address",
"customer": "delivery_customer",
"contact_person": "delivery_contact_name",
"contact_email": "delivery_contact_email",
},
"validation": {"docstatus": ["=", 1]},
},
"Delivery Note Item": {
"doctype": "Shipment Delivery Note",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
"base_amount": "grand_total",
},
},
},
target_doc,
postprocess,
)
return doclist
@frappe.whitelist()
def make_sales_return(source_name: str, target_doc: str | Document | None = None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return make_return_doc("Delivery Note", source_name, target_doc)
@frappe.whitelist()
def make_inter_company_purchase_receipt(source_name: str, target_doc: str | Document | None = None):
return make_inter_company_transaction("Delivery Note", source_name, target_doc)
def make_inter_company_transaction(doctype: str, source_name: str, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
get_inter_company_details,
set_purchase_references,
update_address,
update_taxes,
validate_inter_company_transaction,
)
if doctype == "Delivery Note":
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Receipt"
source_document_warehouse_field = "target_warehouse"
target_document_warehouse_field = "from_warehouse"
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Delivery Note"
source_document_warehouse_field = "from_warehouse"
target_document_warehouse_field = "target_warehouse"
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
def set_missing_values(source, target):
target.run_method("set_missing_values")
set_purchase_references(target)
if target.doctype == "Purchase Receipt":
master_doctype = "Purchase Taxes and Charges Template"
else:
master_doctype = "Sales Taxes and Charges Template"
if not target.get("taxes") and target.get("taxes_and_charges"):
for tax in get_taxes_and_charges(master_doctype, target.get("taxes_and_charges")):
target.append("taxes", tax)
if not target.get("items"):
frappe.throw(_("All items have already been received"))
def update_details(source_doc, target_doc, source_parent):
def _validate_address_link(address, link_doctype, link_name):
return frappe.db.get_value(
"Dynamic Link",
{
"parent": address,
"parenttype": "Address",
"link_doctype": link_doctype,
"link_name": link_name,
},
"parent",
)
target_doc.inter_company_invoice_reference = source_doc.name
if target_doc.doctype == "Purchase Receipt":
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.buying_price_list = source_doc.selling_price_list
target_doc.is_internal_supplier = 1
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
if source_doc.company_address and _validate_address_link(
source_doc.company_address, "Supplier", details.get("party")
):
update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
if source_doc.dispatch_address_name and _validate_address_link(
source_doc.dispatch_address_name, "Company", details.get("company")
):
update_address(
target_doc,
"dispatch_address",
"dispatch_address_display",
source_doc.dispatch_address_name,
)
if source_doc.shipping_address_name and _validate_address_link(
source_doc.shipping_address_name, "Company", details.get("company")
):
update_address(
target_doc,
"shipping_address",
"shipping_address_display",
source_doc.shipping_address_name,
)
if source_doc.customer_address and _validate_address_link(
source_doc.customer_address, "Company", details.get("company")
):
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
)
update_taxes(
target_doc,
party=target_doc.supplier,
party_type="Supplier",
company=target_doc.company,
doctype=target_doc.doctype,
party_address=target_doc.supplier_address,
company_address=target_doc.shipping_address,
)
else:
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
target_doc.company_address = source_doc.supplier_address
target_doc.selling_price_list = source_doc.buying_price_list
target_doc.is_internal_customer = 1
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
if source_doc.supplier_address and _validate_address_link(
source_doc.supplier_address, "Company", details.get("company")
):
update_address(
target_doc, "company_address", "company_address_display", source_doc.supplier_address
)
if source_doc.shipping_address and _validate_address_link(
source_doc.shipping_address, "Customer", details.get("party")
):
update_address(
target_doc, "shipping_address_name", "shipping_address", source_doc.shipping_address
)
if source_doc.shipping_address and _validate_address_link(
source_doc.shipping_address, "Customer", details.get("party")
):
update_address(target_doc, "customer_address", "address_display", source_doc.shipping_address)
update_taxes(
target_doc,
party=target_doc.customer,
party_type="Customer",
company=target_doc.company,
doctype=target_doc.doctype,
party_address=target_doc.customer_address,
company_address=target_doc.company_address,
shipping_address_name=target_doc.shipping_address_name,
)
def update_item(source, target, source_parent):
if source_parent.doctype == "Delivery Note" and source.received_qty:
target.qty = flt(source.qty) + flt(source.returned_qty) - flt(source.received_qty)
if source.get("use_serial_batch_fields"):
target.set("use_serial_batch_fields", 1)
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.set("use_serial_batch_fields", 1)
doclist = get_mapped_doc(
doctype,
source_name,
{
doctype: {
"doctype": target_doctype,
"postprocess": update_details,
"field_no_map": ["taxes_and_charges", "set_warehouse"],
"field_map": {"shipping_address_name": "shipping_address"},
},
doctype + " Item": {
"doctype": target_doctype + " Item",
"field_map": {
source_document_warehouse_field: target_document_warehouse_field,
"name": "delivery_note_item",
"purchase_order": "purchase_order",
"purchase_order_item": "purchase_order_item",
"material_request": "material_request",
"Material_request_item": "material_request_item",
},
"field_no_map": ["warehouse"],
"condition": lambda item: item.received_qty < item.qty + item.returned_qty,
"postprocess": update_item,
},
},
target_doc,
set_missing_values,
)
return doclist