コード例 #1
0
def get_loan_amount(filters):
    total_amount = 0
    for doctype in ["Loan Disbursement", "Loan Repayment"]:
        loan_doc = frappe.qb.DocType(doctype)
        ifnull = CustomFunction("IFNULL", ["value", "default"])

        if doctype == "Loan Disbursement":
            amount_field = Sum(loan_doc.disbursed_amount)
            posting_date = (loan_doc.disbursement_date).as_("posting_date")
            account = loan_doc.disbursement_account
            salary_condition = loan_doc.docstatus == 1
        else:
            amount_field = Sum(loan_doc.amount_paid)
            posting_date = (loan_doc.posting_date).as_("posting_date")
            account = loan_doc.payment_account
            salary_condition = loan_doc.repay_from_salary == 0
        amount = (frappe.qb.from_(loan_doc).select(amount_field).where(
            loan_doc.docstatus == 1).where(salary_condition).where(
                account == filters.get("account")).where(
                    posting_date > getdate(filters.get("report_date"))).where(
                        ifnull(loan_doc.clearance_date, "4000-01-01") <=
                        getdate(filters.get("report_date"))).run()[0][0])

        total_amount += flt(amount)

    return total_amount
コード例 #2
0
def get_batch_incoming_rate(
	item_code, warehouse, batch_no, posting_date, posting_time, creation=None
):

	sle = frappe.qb.DocType("Stock Ledger Entry")

	timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
		posting_date, posting_time
	)
	if creation:
		timestamp_condition |= (
			CombineDatetime(sle.posting_date, sle.posting_time)
			== CombineDatetime(posting_date, posting_time)
		) & (sle.creation < creation)

	batch_details = (
		frappe.qb.from_(sle)
		.select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
		.where(
			(sle.item_code == item_code)
			& (sle.warehouse == warehouse)
			& (sle.batch_no == batch_no)
			& (sle.is_cancelled == 0)
		)
		.where(timestamp_condition)
	).run(as_dict=True)

	if batch_details and batch_details[0].batch_qty:
		return batch_details[0].batch_value / batch_details[0].batch_qty
コード例 #3
0
def get_leave_allocation_records(employee, date, leave_type=None):
	"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
	Ledger = frappe.qb.DocType("Leave Ledger Entry")

	cf_leave_case = (
		frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
	)
	sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")

	new_leaves_case = (
		frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
	)
	sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")

	query = (
		frappe.qb.from_(Ledger)
		.select(
			sum_cf_leaves,
			sum_new_leaves,
			Min(Ledger.from_date).as_("from_date"),
			Max(Ledger.to_date).as_("to_date"),
			Ledger.leave_type,
		)
		.where(
			(Ledger.from_date <= date)
			& (Ledger.to_date >= date)
			& (Ledger.docstatus == 1)
			& (Ledger.transaction_type == "Leave Allocation")
			& (Ledger.employee == employee)
			& (Ledger.is_expired == 0)
			& (Ledger.is_lwp == 0)
		)
	)

	if leave_type:
		query = query.where((Ledger.leave_type == leave_type))
	query = query.groupby(Ledger.employee, Ledger.leave_type)

	allocation_details = query.run(as_dict=True)

	allocated_leaves = frappe._dict()
	for d in allocation_details:
		allocated_leaves.setdefault(
			d.leave_type,
			frappe._dict(
				{
					"from_date": d.from_date,
					"to_date": d.to_date,
					"total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
					"unused_leaves": d.cf_leaves,
					"new_leaves_allocated": d.new_leaves,
					"leave_type": d.leave_type,
				}
			),
		)
	return allocated_leaves
コード例 #4
0
ファイル: bin.py プロジェクト: ankush/erpnext
    def update_reserved_qty_for_sub_contracting(self):
        # reserved qty

        po = frappe.qb.DocType("Purchase Order")
        supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")

        reserved_qty_for_sub_contract = (
            frappe.qb.from_(po).from_(supplied_item).select(
                Sum(Coalesce(supplied_item.required_qty, 0))).where(
                    (supplied_item.rm_item_code == self.item_code)
                    & (po.name == supplied_item.parent)
                    & (po.docstatus == 1)
                    & (po.is_subcontracted)
                    & (po.status != "Closed")
                    & (po.per_received < 100)
                    & (supplied_item.reserve_warehouse == self.warehouse))
        ).run()[0][0] or 0.0

        se = frappe.qb.DocType("Stock Entry")
        se_item = frappe.qb.DocType("Stock Entry Detail")

        if frappe.db.field_exists("Stock Entry", "is_return"):
            qty_field = (Case().when(se.is_return == 1, se_item.transfer_qty *
                                     -1).else_(se_item.transfer_qty))
        else:
            qty_field = se_item.transfer_qty

        materials_transferred = (
            frappe.qb.from_(se).from_(se_item).from_(po).select(
                Sum(qty_field)).where(
                    (se.docstatus == 1)
                    & (se.purpose == "Send to Subcontractor")
                    & (Coalesce(se.purchase_order, "") != "")
                    & ((se_item.item_code == self.item_code) |
                       (se_item.original_item == self.item_code))
                    & (se.name == se_item.parent)
                    & (po.name == se.purchase_order)
                    & (po.docstatus == 1)
                    & (po.is_subcontracted == 1)
                    & (po.status != "Closed")
                    & (po.per_received < 100))).run()[0][0] or 0.0

        if reserved_qty_for_sub_contract > materials_transferred:
            reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred
        else:
            reserved_qty_for_sub_contract = 0

        self.db_set("reserved_qty_for_sub_contract",
                    reserved_qty_for_sub_contract)
        self.set_projected_qty()
        self.db_set("projected_qty", self.projected_qty)
コード例 #5
0
def get_data(filters):
    mr = frappe.qb.DocType("Material Request")
    mr_item = frappe.qb.DocType("Material Request Item")

    query = (frappe.qb.from_(mr).join(mr_item).on(
        mr_item.parent == mr.name).select(
            mr.name.as_("material_request"),
            mr.transaction_date.as_("date"),
            mr_item.schedule_date.as_("required_date"),
            mr_item.item_code.as_("item_code"),
            Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
            Coalesce(mr_item.stock_uom, "").as_("uom"),
            Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
            Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
            (Sum(Coalesce(mr_item.stock_qty, 0)) -
             Sum(Coalesce(mr_item.received_qty, 0))).as_("qty_to_receive"),
            Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
            (Sum(Coalesce(mr_item.stock_qty, 0)) -
             Sum(Coalesce(mr_item.ordered_qty, 0))).as_("qty_to_order"),
            mr_item.item_name,
            mr_item.description,
            mr.company,
        ).where((mr.material_request_type == "Purchase")
                & (mr.docstatus == 1)
                & (mr.status != "Stopped")
                & (mr.per_received < 100)))

    query = get_conditions(filters, query, mr,
                           mr_item)  # add conditional conditions

    query = query.groupby(mr.name,
                          mr_item.item_code).orderby(mr.transaction_date,
                                                     mr.schedule_date)
    data = query.run(as_dict=True)
    return data
コード例 #6
0
def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
	"""Get total reserved quantity for any item in specified warehouse"""
	wo = frappe.qb.DocType("Work Order")
	wo_item = frappe.qb.DocType("Work Order Item")

	return (
		frappe.qb.from_(wo)
		.from_(wo_item)
		.select(
			Sum(
				Case()
				.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
				.else_(wo_item.required_qty - wo_item.consumed_qty)
			)
		)
		.where(
			(wo_item.item_code == item_code)
			& (wo_item.parent == wo.name)
			& (wo.docstatus == 1)
			& (wo_item.source_warehouse == warehouse)
			& (wo.status.notin(["Stopped", "Completed", "Closed"]))
			& (
				(wo_item.required_qty > wo_item.transferred_qty)
				| (wo_item.required_qty > wo_item.consumed_qty)
			)
		)
	).run()[0][0] or 0.0
コード例 #7
0
def get_leave_summary(employee: str, filters: Filters) -> Dict[str, float]:
    """Returns a dict of leave type and corresponding leaves taken by employee like:
	{'leave_without_pay': 1.0, 'sick_leave': 2.0}
	"""
    Attendance = frappe.qb.DocType("Attendance")
    day_case = frappe.qb.terms.Case().when(Attendance.status == "Half Day",
                                           0.5).else_(1)
    sum_leave_days = Sum(day_case).as_("leave_days")

    leave_details = (frappe.qb.from_(Attendance).select(
        Attendance.leave_type, sum_leave_days).where(
            (Attendance.employee == employee)
            & (Attendance.docstatus == 1)
            & (Attendance.company == filters.company)
            & ((Attendance.leave_type.isnotnull())
               | (Attendance.leave_type != ""))
            & (Extract("month", Attendance.attendance_date) == filters.month)
            & (Extract("year", Attendance.attendance_date) == filters.year)).
                     groupby(Attendance.leave_type)).run(as_dict=True)

    leaves = {}
    for d in leave_details:
        leave_type = frappe.scrub(d.leave_type)
        leaves[leave_type] = d.leave_days

    return leaves
コード例 #8
0
def get_attendance_summary_and_days(employee: str,
                                    filters: Filters) -> Tuple[Dict, List]:
    Attendance = frappe.qb.DocType("Attendance")

    present_case = (frappe.qb.terms.Case().when(
        ((Attendance.status == "Present") |
         (Attendance.status == "Work From Home")), 1).else_(0))
    sum_present = Sum(present_case).as_("total_present")

    absent_case = frappe.qb.terms.Case().when(Attendance.status == "Absent",
                                              1).else_(0)
    sum_absent = Sum(absent_case).as_("total_absent")

    leave_case = frappe.qb.terms.Case().when(Attendance.status == "On Leave",
                                             1).else_(0)
    sum_leave = Sum(leave_case).as_("total_leaves")

    half_day_case = frappe.qb.terms.Case().when(
        Attendance.status == "Half Day", 0.5).else_(0)
    sum_half_day = Sum(half_day_case).as_("total_half_days")

    summary = (frappe.qb.from_(Attendance).select(
        sum_present,
        sum_absent,
        sum_leave,
        sum_half_day,
    ).where((Attendance.docstatus == 1)
            & (Attendance.employee == employee)
            & (Attendance.company == filters.company)
            & (Extract("month", Attendance.attendance_date) == filters.month)
            & (Extract("year", Attendance.attendance_date) == filters.year))
               ).run(as_dict=True)

    days = (frappe.qb.from_(Attendance).select(
        Extract("day", Attendance.attendance_date).as_("day_of_month")
    ).distinct().where(
        (Attendance.docstatus == 1)
        & (Attendance.employee == employee)
        & (Attendance.company == filters.company)
        & (Extract("month", Attendance.attendance_date) == filters.month)
        & (Extract("year", Attendance.attendance_date) == filters.year))).run(
            pluck=True)

    return summary[0], days
コード例 #9
0
    def set_total_advance_paid(self):
        gle = frappe.qb.DocType("GL Entry")

        paid_amount = (frappe.qb.from_(gle).select(
            Sum(gle.debit).as_("paid_amount")).where(
                (gle.against_voucher_type == "Employee Advance")
                & (gle.against_voucher == self.name)
                & (gle.party_type == "Employee")
                & (gle.party == self.employee)
                & (gle.docstatus == 1)
                & (gle.is_cancelled == 0))).run(
                    as_dict=True)[0].paid_amount or 0

        return_amount = (frappe.qb.from_(gle).select(
            Sum(gle.credit).as_("return_amount")).where(
                (gle.against_voucher_type == "Employee Advance")
                & (gle.voucher_type != "Expense Claim")
                & (gle.against_voucher == self.name)
                & (gle.party_type == "Employee")
                & (gle.party == self.employee)
                & (gle.docstatus == 1)
                & (gle.is_cancelled == 0))).run(
                    as_dict=True)[0].return_amount or 0

        if paid_amount != 0:
            paid_amount = flt(paid_amount) / flt(self.exchange_rate)
        if return_amount != 0:
            return_amount = flt(return_amount) / flt(self.exchange_rate)

        if flt(paid_amount) > self.advance_amount:
            frappe.throw(
                _("Row {0}# Paid Amount cannot be greater than requested advance amount"
                  ),
                EmployeeAdvanceOverPayment,
            )

        if flt(return_amount) > self.paid_amount - self.claimed_amount:
            frappe.throw(_("Return amount cannot be greater unclaimed amount"))

        self.db_set("paid_amount", paid_amount)
        self.db_set("return_amount", return_amount)
        self.set_status(update=True)
コード例 #10
0
    def get_ctc(self):
        # Get total earnings from existing salary slip
        ss = frappe.qb.DocType("Salary Slip")
        existing_ss = frappe._dict(
            (frappe.qb.from_(ss).select(ss.employee,
                                        Sum(ss.base_gross_pay).as_("amount")).
             where(ss.docstatus == 1).where(
                 ss.employee.isin(list(self.employees.keys()))).where(
                     ss.start_date >= self.payroll_period_start_date).where(
                         ss.end_date <= self.payroll_period_end_date).groupby(
                             ss.employee)).run())

        for employee in list(self.employees.keys()):
            future_ss_earnings = self.get_future_earnings(employee)
            ctc = flt(existing_ss.get(employee)) + future_ss_earnings

            self.employees[employee].setdefault("ctc", ctc)
コード例 #11
0
ファイル: leave_allocation.py プロジェクト: ankush/erpnext
def get_leave_allocation_for_period(employee,
                                    leave_type,
                                    from_date,
                                    to_date,
                                    exclude_allocation=None):
    from frappe.query_builder.functions import Sum

    Allocation = frappe.qb.DocType("Leave Allocation")
    return (frappe.qb.from_(Allocation).select(
        Sum(Allocation.total_leaves_allocated).as_("total_allocated_leaves")).
            where((Allocation.employee == employee)
                  & (Allocation.leave_type == leave_type)
                  & (Allocation.docstatus == 1)
                  & (Allocation.name != exclude_allocation)
                  & ((Allocation.from_date.between(from_date, to_date))
                     | (Allocation.to_date.between(from_date, to_date))
                     | ((Allocation.from_date < from_date) &
                        (Allocation.to_date > to_date))))).run()[0][0] or 0.0
コード例 #12
0
    def get_tax_exemptions(self, source):
        # Get category-wise exmeptions based on submitted proofs or declarations
        if source == "Employee Tax Exemption Proof Submission":
            child_doctype = "Employee Tax Exemption Proof Submission Detail"
        else:
            child_doctype = "Employee Tax Exemption Declaration Category"

        max_exemptions = self.get_max_exemptions_based_on_category()

        par = frappe.qb.DocType(source)
        child = frappe.qb.DocType(child_doctype)

        records = (frappe.qb.from_(par).inner_join(child).on(
            par.name == child.parent).select(
                par.employee, child.exemption_category,
                Sum(child.amount).as_("amount")
            ).where(par.docstatus == 1).where(
                par.employee.isin(list(self.employees.keys()))).where(
                    par.payroll_period == self.filters.payroll_period).groupby(
                        par.employee,
                        child.exemption_category)).run(as_dict=True)

        for d in records:
            if not self.employees[d.employee]["allow_tax_exemption"]:
                continue

            if source == "Employee Tax Exemption Declaration" and d.employee in self.employees_with_proofs:
                continue

            amount = flt(d.amount)
            max_eligible_amount = flt(max_exemptions.get(d.exemption_category))
            if max_eligible_amount and amount > max_eligible_amount:
                amount = max_eligible_amount

            self.employees[d.employee].setdefault(scrub(d.exemption_category),
                                                  amount)
            self.add_to_total_exemption(d.employee, amount)

            if (source == "Employee Tax Exemption Proof Submission"
                    and d.employee not in self.employees_with_proofs):
                self.employees_with_proofs.append(d.employee)
コード例 #13
0
ファイル: job_card.py プロジェクト: erpnext-tm/erpnext
    def set_transferred_qty_in_job_card_item(self, ste_doc):
        from frappe.query_builder.functions import Sum

        def _validate_over_transfer(row, transferred_qty):
            "Block over transfer of items if not allowed in settings."
            required_qty = frappe.db.get_value("Job Card Item",
                                               row.job_card_item,
                                               "required_qty")
            is_excess = flt(transferred_qty) > flt(required_qty)
            if is_excess:
                frappe.throw(
                    _("Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}"
                      ).format(row.idx, frappe.bold(required_qty),
                               frappe.bold(row.item_code), ste_doc.job_card),
                    title=_("Excess Transfer"),
                    exc=JobCardOverTransferError,
                )

        for row in ste_doc.items:
            if not row.job_card_item:
                continue

            sed = frappe.qb.DocType("Stock Entry Detail")
            se = frappe.qb.DocType("Stock Entry")
            transferred_qty = (frappe.qb.from_(sed).join(se).on(
                sed.parent == se.name).select(Sum(sed.qty)).where(
                    (sed.job_card_item == row.job_card_item)
                    & (se.docstatus == 1)
                    & (se.purpose == "Material Transfer for Manufacture"))
                               ).run()[0][0]

            allow_excess = frappe.db.get_single_value(
                "Manufacturing Settings", "job_card_excess_transfer")
            if not allow_excess:
                _validate_over_transfer(row, transferred_qty)

            frappe.db.set_value("Job Card Item", row.job_card_item,
                                "transferred_qty", flt(transferred_qty))
コード例 #14
0
    def get_total_deducted_tax(self):
        self.add_column("Total Tax Deducted")

        ss = frappe.qb.DocType("Salary Slip")
        ss_ded = frappe.qb.DocType("Salary Detail")

        records = (
            frappe.qb.from_(ss).inner_join(ss_ded).on(
                ss.name == ss_ded.parent).select(
                    ss.employee,
                    Sum(ss_ded.amount).as_("amount")).where(
                        ss.docstatus == 1).where(
                            ss.employee.isin(list(
                                self.employees.keys()))).where(
                                    ss_ded.parentfield == "deductions").
            where(ss_ded.variable_based_on_taxable_salary == 1).where(
                ss.start_date >= self.payroll_period_start_date).where(
                    ss.end_date <= self.payroll_period_end_date).groupby(
                        ss.employee)).run(as_dict=True)

        for d in records:
            self.employees[d.employee].setdefault("total_tax_deducted",
                                                  d.amount)
コード例 #15
0
def get_pos_reserved_batch_qty(filters):
    import json

    from frappe.query_builder.functions import Sum

    if isinstance(filters, str):
        filters = json.loads(filters)

    p = frappe.qb.DocType("POS Invoice").as_("p")
    item = frappe.qb.DocType("POS Invoice Item").as_("item")
    sum_qty = Sum(item.qty).as_("qty")

    reserved_batch_qty = (frappe.qb.from_(p).from_(item).select(sum_qty).where(
        (p.name == item.parent)
        & (p.consolidated_invoice.isnull())
        & (p.status != "Consolidated")
        & (p.docstatus == 1)
        & (item.docstatus == 1)
        & (item.item_code == filters.get("item_code"))
        & (item.warehouse == filters.get("warehouse"))
        & (item.batch_no == filters.get("batch_no"))).run())

    flt_reserved_batch_qty = flt(reserved_batch_qty[0][0])
    return flt_reserved_batch_qty
コード例 #16
0
    def get_tax_exempted_earnings_and_deductions(self):
        tax_exempted_components = self.get_tax_exempted_components()

        # Get component totals from existing salary slips
        ss = frappe.qb.DocType("Salary Slip")
        ss_comps = frappe.qb.DocType("Salary Detail")

        records = (
            frappe.qb.from_(ss).inner_join(ss_comps).on(
                ss.name == ss_comps.parent).select(
                    ss.name, ss.employee, ss_comps.salary_component,
                    Sum(ss_comps.amount).as_("amount")).where(
                        ss.docstatus == 1).where(
                            ss.employee.isin(list(self.employees.keys()))).
            where(
                ss_comps.salary_component.isin(tax_exempted_components)).where(
                    ss.start_date >= self.payroll_period_start_date).where(
                        ss.end_date <= self.payroll_period_end_date).groupby(
                            ss.employee,
                            ss_comps.salary_component)).run(as_dict=True)

        existing_ss_exemptions = frappe._dict()
        for d in records:
            existing_ss_exemptions.setdefault(d.employee, {}).setdefault(
                scrub(d.salary_component), d.amount)

        for employee in list(self.employees.keys()):
            if not self.employees[employee]["allow_tax_exemption"]:
                continue

            exemptions = existing_ss_exemptions.get(employee, {})
            self.add_exemptions_from_future_salary_slips(employee, exemptions)
            self.employees[employee].update(exemptions)

            total_exemptions = sum(list(exemptions.values()))
            self.add_to_total_exemption(employee, total_exemptions)
コード例 #17
0
ファイル: custom_cash_flow.py プロジェクト: ankush/erpnext
def _get_account_type_based_data(
	filters, account_names, period_list, accumulated_values, opening_balances=0
):
	if not account_names or not account_names[0] or not type(account_names[0]) == str:
		# only proceed if account_names is a list of account names
		return {}

	from erpnext.accounts.report.cash_flow.cash_flow import get_start_date

	company = filters.company
	data = {}
	total = 0
	GLEntry = frappe.qb.DocType("GL Entry")
	Account = frappe.qb.DocType("Account")

	for period in period_list:
		start_date = get_start_date(period, accumulated_values, company)

		account_subquery = (
			frappe.qb.from_(Account)
			.where((Account.name.isin(account_names)) | (Account.parent_account.isin(account_names)))
			.select(Account.name)
			.as_("account_subquery")
		)

		if opening_balances:
			date_info = dict(date=start_date)
			months_map = {"Monthly": -1, "Quarterly": -3, "Half-Yearly": -6}
			years_map = {"Yearly": -1}

			if months_map.get(filters.periodicity):
				date_info.update(months=months_map[filters.periodicity])
			else:
				date_info.update(years=years_map[filters.periodicity])

			if accumulated_values:
				start, end = add_to_date(start_date, years=-1), add_to_date(period["to_date"], years=-1)
			else:
				start, end = add_to_date(**date_info), add_to_date(**date_info)

			start, end = get_date_str(start), get_date_str(end)

		else:
			start, end = start_date if accumulated_values else period["from_date"], period["to_date"]
			start, end = get_date_str(start), get_date_str(end)

		result = (
			frappe.qb.from_(GLEntry)
			.select(Sum(GLEntry.credit) - Sum(GLEntry.debit))
			.where(
				(GLEntry.company == company)
				& (GLEntry.posting_date >= start)
				& (GLEntry.posting_date <= end)
				& (GLEntry.voucher_type != "Period Closing Voucher")
				& (GLEntry.account.isin(account_subquery))
			)
		).run()

		if result and result[0]:
			gl_sum = result[0][0]
		else:
			gl_sum = 0

		total += flt(gl_sum)
		data.setdefault(period["key"], flt(gl_sum))

	data["total"] = total
	return data
コード例 #18
0
 def sum(self, dt, fieldname, filters=None, **kwargs):
     return self.query.build_conditions(dt, filters=filters).select(
         Sum(Column(fieldname))).run(**kwargs)[0][0] or 0
コード例 #19
0
ファイル: delivery_note.py プロジェクト: erpnext-tm/erpnext
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_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
    sum_amount = Sum(si_item.amount).as_("amount")

    billed_against_so = (frappe.qb.from_(si_item).select(sum_amount).where(
        (si_item.so_detail == so_detail)
        & ((si_item.dn_detail.isnull()) | (si_item.dn_detail == ""))
        & (si_item.docstatus == 1)).run())
    billed_against_so = billed_against_so and billed_against_so[0][0] or 0

    # Get all Delivery Note Item rows against the Sales Order Item row
    dn = frappe.qb.DocType("Delivery Note").as_("dn")
    dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")

    dn_details = (frappe.qb.from_(dn).from_(dn_item).select(
        dn_item.name, dn_item.amount, dn_item.si_detail,
        dn_item.parent).where((dn.name == dn_item.parent)
                              & (dn_item.so_detail == so_detail)
                              & (dn.docstatus == 1)
                              & (dn.is_return == 0)).orderby(
                                  dn.posting_date, dn.posting_time,
                                  dn.name).run(as_dict=True))

    updated_dn = []
    for dnd in dn_details:
        billed_amt_agianst_dn = 0

        # If delivered against Sales Invoice
        if dnd.si_detail:
            billed_amt_agianst_dn = flt(dnd.amount)
            billed_against_so -= billed_amt_agianst_dn
        else:
            # Get billed amount directly against Delivery Note
            billed_amt_agianst_dn = frappe.db.sql(
                """select sum(amount) from `tabSales Invoice Item`
				where dn_detail=%s and docstatus=1""",
                dnd.name,
            )
            billed_amt_agianst_dn = billed_amt_agianst_dn and billed_amt_agianst_dn[
                0][0] or 0

        # Distribute billed amount directly against SO between DNs based on FIFO
        if billed_against_so and billed_amt_agianst_dn < dnd.amount:
            pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
            if pending_to_bill <= billed_against_so:
                billed_amt_agianst_dn += pending_to_bill
                billed_against_so -= pending_to_bill
            else:
                billed_amt_agianst_dn += billed_against_so
                billed_against_so = 0

        frappe.db.set_value(
            "Delivery Note Item",
            dnd.name,
            "billed_amt",
            billed_amt_agianst_dn,
            update_modified=update_modified,
        )

        updated_dn.append(dnd.parent)

    return updated_dn