mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-26 21:35:19 +00:00
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
@@ -510,6 +510,7 @@ class GrossProfitGenerator:
|
|||||||
self.average_buying_rate = {}
|
self.average_buying_rate = {}
|
||||||
self.filters = frappe._dict(filters)
|
self.filters = frappe._dict(filters)
|
||||||
self.load_invoice_items()
|
self.load_invoice_items()
|
||||||
|
self.load_drop_ship_buying_rates()
|
||||||
self.get_delivery_notes()
|
self.get_delivery_notes()
|
||||||
|
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
@@ -641,7 +642,11 @@ class GrossProfitGenerator:
|
|||||||
returned_item_row.qty += row.qty
|
returned_item_row.qty += row.qty
|
||||||
returned_item_row.base_amount += row.base_amount
|
returned_item_row.base_amount += row.base_amount
|
||||||
|
|
||||||
if not row.delivered_by_supplier:
|
if row.delivered_by_supplier:
|
||||||
|
buying_amount = self.get_drop_ship_buying_amount(row)
|
||||||
|
if buying_amount is not None:
|
||||||
|
row.buying_amount = flt(buying_amount, self.currency_precision)
|
||||||
|
else:
|
||||||
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
|
|
||||||
def get_average_rate_based_on_group_by(self):
|
def get_average_rate_based_on_group_by(self):
|
||||||
@@ -781,28 +786,12 @@ class GrossProfitGenerator:
|
|||||||
# IMP NOTE
|
# IMP NOTE
|
||||||
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
||||||
# sorted by posting_date desc, posting_time desc
|
# sorted by posting_date desc, posting_time desc
|
||||||
if (
|
if row.delivered_by_supplier:
|
||||||
row.delivered_by_supplier
|
buying_amount = self.get_drop_ship_buying_amount(row)
|
||||||
and row.so_detail
|
if buying_amount is not None:
|
||||||
and (
|
return buying_amount
|
||||||
po_details := frappe.get_all(
|
|
||||||
"Purchase Order Item",
|
|
||||||
filters={"sales_order_item": row.so_detail, "docstatus": 1},
|
|
||||||
pluck="name",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
from frappe.query_builder.functions import Sum
|
|
||||||
|
|
||||||
table = frappe.qb.DocType("Purchase Invoice Item")
|
if item_code in self.non_stock_items and (row.project or row.cost_center):
|
||||||
query = (
|
|
||||||
frappe.qb.from_(table)
|
|
||||||
.select(Sum(table.qty * table.base_net_rate))
|
|
||||||
.where((table.po_detail.isin(po_details)) & (table.docstatus == 1))
|
|
||||||
)
|
|
||||||
return flt(query.run()[0][0])
|
|
||||||
|
|
||||||
elif item_code in self.non_stock_items and (row.project or row.cost_center):
|
|
||||||
# Issue 6089-Get last purchasing rate for non-stock item
|
# Issue 6089-Get last purchasing rate for non-stock item
|
||||||
item_rate = self.get_last_purchase_rate(item_code, row)
|
item_rate = self.get_last_purchase_rate(item_code, row)
|
||||||
return flt(row.qty) * item_rate
|
return flt(row.qty) * item_rate
|
||||||
@@ -833,6 +822,49 @@ class GrossProfitGenerator:
|
|||||||
|
|
||||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||||
|
|
||||||
|
def load_drop_ship_buying_rates(self):
|
||||||
|
self.drop_ship_buying_rates = {}
|
||||||
|
sales_order_items = {
|
||||||
|
row.so_detail for row in self.si_list if row.delivered_by_supplier and row.so_detail
|
||||||
|
}
|
||||||
|
if not sales_order_items:
|
||||||
|
return
|
||||||
|
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
|
purchase_order_item = frappe.qb.DocType("Purchase Order Item")
|
||||||
|
purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
|
buying_amounts = (
|
||||||
|
frappe.qb.from_(purchase_order_item)
|
||||||
|
.left_join(purchase_invoice_item)
|
||||||
|
.on(
|
||||||
|
(purchase_invoice_item.po_detail == purchase_order_item.name)
|
||||||
|
& (purchase_invoice_item.docstatus == 1)
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
purchase_order_item.sales_order_item,
|
||||||
|
Sum(purchase_invoice_item.qty * purchase_invoice_item.base_net_rate).as_("buying_amount"),
|
||||||
|
Sum(purchase_invoice_item.stock_qty).as_("stock_qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(purchase_order_item.sales_order_item.isin(sales_order_items))
|
||||||
|
& (purchase_order_item.docstatus == 1)
|
||||||
|
)
|
||||||
|
.groupby(purchase_order_item.sales_order_item)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in buying_amounts:
|
||||||
|
self.drop_ship_buying_rates[row.sales_order_item] = (
|
||||||
|
flt(row.buying_amount) / flt(row.stock_qty) if flt(row.stock_qty) else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_drop_ship_buying_amount(self, row):
|
||||||
|
if row.so_detail not in self.drop_ship_buying_rates:
|
||||||
|
return
|
||||||
|
|
||||||
|
return flt(row.qty) * self.drop_ship_buying_rates[row.so_detail]
|
||||||
|
|
||||||
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
||||||
from frappe.query_builder.functions import Avg
|
from frappe.query_builder.functions import Avg
|
||||||
|
|
||||||
|
|||||||
@@ -676,19 +676,9 @@ class TestGrossProfit(ERPNextTestSuite):
|
|||||||
self.assertEqual(total[8], 0.0) # gross profit %
|
self.assertEqual(total[8], 0.0) # gross profit %
|
||||||
|
|
||||||
def test_drop_ship(self):
|
def test_drop_ship(self):
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order, make_sales_invoice
|
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
|
||||||
|
|
||||||
item = make_item("_Test Drop Ship Item", properties={"is_stock_item": 1, "delivered_by_supplier": 1})
|
so = self.create_drop_ship_order()
|
||||||
|
|
||||||
so = make_sales_order(item=item.name, qty=10, rate=100)
|
|
||||||
po = make_purchase_order(so.name, selected_items=[so.items[0]])[0]
|
|
||||||
po.items[0].rate = 80
|
|
||||||
po.supplier = "_Test Supplier"
|
|
||||||
po.submit()
|
|
||||||
make_purchase_invoice(po.name).submit()
|
|
||||||
si = make_sales_invoice(so.name).submit()
|
si = make_sales_invoice(so.name).submit()
|
||||||
|
|
||||||
filters = frappe._dict(
|
filters = frappe._dict(
|
||||||
@@ -700,6 +690,58 @@ class TestGrossProfit(ERPNextTestSuite):
|
|||||||
self.assertIsNone(data[1].buying_rate)
|
self.assertIsNone(data[1].buying_rate)
|
||||||
self.assertEqual(data[1]["gross_profit_%"], 20)
|
self.assertEqual(data[1]["gross_profit_%"], 20)
|
||||||
|
|
||||||
|
def test_drop_ship_partial_billing_and_return(self):
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
|
|
||||||
|
so = self.create_drop_ship_order()
|
||||||
|
first_invoice = make_sales_invoice(so.name)
|
||||||
|
first_invoice.items[0].qty = 4
|
||||||
|
first_invoice.submit()
|
||||||
|
second_invoice = make_sales_invoice(so.name).submit()
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company=first_invoice.company,
|
||||||
|
from_date=first_invoice.posting_date,
|
||||||
|
to_date=first_invoice.posting_date,
|
||||||
|
group_by="Invoice",
|
||||||
|
)
|
||||||
|
_, data = execute(filters=filters)
|
||||||
|
invoice_rows = {
|
||||||
|
row.parent_invoice: row
|
||||||
|
for row in data
|
||||||
|
if row.parent_invoice in {first_invoice.name, second_invoice.name} and row.indent == 1
|
||||||
|
}
|
||||||
|
self.assertEqual(invoice_rows[first_invoice.name].buying_amount, 320)
|
||||||
|
self.assertEqual(invoice_rows[second_invoice.name].buying_amount, 480)
|
||||||
|
|
||||||
|
sales_return = make_sales_return(first_invoice.name)
|
||||||
|
sales_return.items[0].qty = -2
|
||||||
|
sales_return.submit()
|
||||||
|
|
||||||
|
_, data = execute(filters=filters)
|
||||||
|
first_invoice_row = next(
|
||||||
|
row for row in data if row.parent_invoice == first_invoice.name and row.indent == 1
|
||||||
|
)
|
||||||
|
self.assertEqual(first_invoice_row.qty, 2)
|
||||||
|
self.assertEqual(first_invoice_row.buying_amount, 160)
|
||||||
|
self.assertEqual(first_invoice_row.gross_profit, 40)
|
||||||
|
|
||||||
|
def create_drop_ship_order(self, qty=10, selling_rate=100, buying_rate=80):
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
|
item = make_item("_Test Drop Ship Item", properties={"is_stock_item": 1, "delivered_by_supplier": 1})
|
||||||
|
so = make_sales_order(item=item.name, qty=qty, rate=selling_rate)
|
||||||
|
purchase_order = make_purchase_order(so.name, selected_items=[so.items[0]])[0]
|
||||||
|
purchase_order.items[0].rate = buying_rate
|
||||||
|
purchase_order.supplier = "_Test Supplier"
|
||||||
|
purchase_order.submit()
|
||||||
|
make_purchase_invoice(purchase_order.name).submit()
|
||||||
|
|
||||||
|
return so
|
||||||
|
|
||||||
def create_rate_adjustment_debit_note(self, against_invoice, adjustment_rate, item_code=None):
|
def create_rate_adjustment_debit_note(self, against_invoice, adjustment_rate, item_code=None):
|
||||||
"""Create a rate adjustment debit note with no stock movement."""
|
"""Create a rate adjustment debit note with no stock movement."""
|
||||||
dn = self.create_sales_invoice(qty=1, rate=adjustment_rate, do_not_save=True, do_not_submit=True)
|
dn = self.create_sales_invoice(qty=1, rate=adjustment_rate, do_not_save=True, do_not_submit=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user