Merge branch 'develop' into fix_date_range_print
This commit is contained in:
commit
934cb97d90
@ -346,9 +346,13 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
|
accounting_dimensions = get_accounting_dimensions(as_list=False) or []
|
||||||
|
accounting_dimensions_list = [d.fieldname for d in accounting_dimensions]
|
||||||
|
|
||||||
if filters.get("company"):
|
if filters.get("company"):
|
||||||
conditions += " and company=%(company)s"
|
conditions += " and company=%(company)s"
|
||||||
if filters.get("customer"):
|
|
||||||
|
if filters.get("customer") and "customer" not in accounting_dimensions_list:
|
||||||
conditions += " and customer = %(customer)s"
|
conditions += " and customer = %(customer)s"
|
||||||
|
|
||||||
if filters.get("from_date"):
|
if filters.get("from_date"):
|
||||||
@ -359,32 +363,18 @@ def get_conditions(filters):
|
|||||||
if filters.get("owner"):
|
if filters.get("owner"):
|
||||||
conditions += " and owner = %(owner)s"
|
conditions += " and owner = %(owner)s"
|
||||||
|
|
||||||
if filters.get("mode_of_payment"):
|
def get_sales_invoice_item_field_condition(field, table="Sales Invoice Item") -> str:
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
if not filters.get(field) or field in accounting_dimensions_list:
|
||||||
|
return ""
|
||||||
|
return f""" and exists(select name from `tab{table}`
|
||||||
where parent=`tabSales Invoice`.name
|
where parent=`tabSales Invoice`.name
|
||||||
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
|
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||||
|
|
||||||
if filters.get("cost_center"):
|
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment")
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Item`
|
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||||
where parent=`tabSales Invoice`.name
|
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||||
and ifnull(`tabSales Invoice Item`.cost_center, '') = %(cost_center)s)"""
|
conditions += get_sales_invoice_item_field_condition("brand")
|
||||||
|
conditions += get_sales_invoice_item_field_condition("item_group")
|
||||||
if filters.get("warehouse"):
|
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Item`
|
|
||||||
where parent=`tabSales Invoice`.name
|
|
||||||
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
|
|
||||||
|
|
||||||
if filters.get("brand"):
|
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Item`
|
|
||||||
where parent=`tabSales Invoice`.name
|
|
||||||
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
|
|
||||||
|
|
||||||
if filters.get("item_group"):
|
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Item`
|
|
||||||
where parent=`tabSales Invoice`.name
|
|
||||||
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
|
||||||
|
|
||||||
if accounting_dimensions:
|
if accounting_dimensions:
|
||||||
common_condition = """
|
common_condition = """
|
||||||
|
@ -621,7 +621,7 @@ class JobCard(Document):
|
|||||||
self.set_status(update_status)
|
self.set_status(update_status)
|
||||||
|
|
||||||
def set_status(self, update_status=False):
|
def set_status(self, update_status=False):
|
||||||
if self.status == "On Hold":
|
if self.status == "On Hold" and self.docstatus == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
|
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
|
||||||
|
@ -373,3 +373,4 @@ erpnext.patches.v13_0.create_accounting_dimensions_in_orders
|
|||||||
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
||||||
execute:frappe.delete_doc("DocType", "Naming Series")
|
execute:frappe.delete_doc("DocType", "Naming Series")
|
||||||
erpnext.patches.v13_0.set_payroll_entry_status
|
erpnext.patches.v13_0.set_payroll_entry_status
|
||||||
|
erpnext.patches.v13_0.job_card_status_on_hold
|
||||||
|
19
erpnext/patches/v13_0/job_card_status_on_hold.py
Normal file
19
erpnext/patches/v13_0/job_card_status_on_hold.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
job_cards = frappe.get_all(
|
||||||
|
"Job Card",
|
||||||
|
{"status": "On Hold", "docstatus": ("!=", 0)},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
for idx, job_card in enumerate(job_cards):
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
doc.set_status()
|
||||||
|
doc.db_set("status", doc.status, update_modified=False)
|
||||||
|
if idx % 100 == 0:
|
||||||
|
frappe.db.commit()
|
||||||
|
except Exception:
|
||||||
|
continue
|
@ -1,11 +1,13 @@
|
|||||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import CustomFunction
|
||||||
|
from frappe.query_builder.functions import Max
|
||||||
from frappe.utils import date_diff, flt, getdate
|
from frappe.utils import date_diff, flt, getdate
|
||||||
|
|
||||||
|
|
||||||
@ -18,11 +20,12 @@ def execute(filters=None):
|
|||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
data = get_data(conditions, filters)
|
data = get_data(conditions, filters)
|
||||||
|
so_elapsed_time = get_so_elapsed_time(data)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return [], [], None, []
|
return [], [], None, []
|
||||||
|
|
||||||
data, chart_data = prepare_data(data, filters)
|
data, chart_data = prepare_data(data, so_elapsed_time, filters)
|
||||||
|
|
||||||
return columns, data, None, chart_data
|
return columns, data, None, chart_data
|
||||||
|
|
||||||
@ -65,7 +68,6 @@ def get_data(conditions, filters):
|
|||||||
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
|
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
|
||||||
soi.qty, soi.delivered_qty,
|
soi.qty, soi.delivered_qty,
|
||||||
(soi.qty - soi.delivered_qty) AS pending_qty,
|
(soi.qty - soi.delivered_qty) AS pending_qty,
|
||||||
IF((SELECT pending_qty) = 0, (TO_SECONDS(Max(dn.posting_date))-TO_SECONDS(so.transaction_date)), 0) as time_taken_to_deliver,
|
|
||||||
IFNULL(SUM(sii.qty), 0) as billed_qty,
|
IFNULL(SUM(sii.qty), 0) as billed_qty,
|
||||||
soi.base_amount as amount,
|
soi.base_amount as amount,
|
||||||
(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
|
(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
|
||||||
@ -76,13 +78,9 @@ def get_data(conditions, filters):
|
|||||||
soi.description as description
|
soi.description as description
|
||||||
FROM
|
FROM
|
||||||
`tabSales Order` so,
|
`tabSales Order` so,
|
||||||
(`tabSales Order Item` soi
|
`tabSales Order Item` soi
|
||||||
LEFT JOIN `tabSales Invoice Item` sii
|
LEFT JOIN `tabSales Invoice Item` sii
|
||||||
ON sii.so_detail = soi.name and sii.docstatus = 1)
|
ON sii.so_detail = soi.name and sii.docstatus = 1
|
||||||
LEFT JOIN `tabDelivery Note Item` dni
|
|
||||||
on dni.so_detail = soi.name
|
|
||||||
LEFT JOIN `tabDelivery Note` dn
|
|
||||||
on dni.parent = dn.name and dn.docstatus = 1
|
|
||||||
WHERE
|
WHERE
|
||||||
soi.parent = so.name
|
soi.parent = so.name
|
||||||
and so.status not in ('Stopped', 'Closed', 'On Hold')
|
and so.status not in ('Stopped', 'Closed', 'On Hold')
|
||||||
@ -100,7 +98,48 @@ def get_data(conditions, filters):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def prepare_data(data, filters):
|
def get_so_elapsed_time(data):
|
||||||
|
"""
|
||||||
|
query SO's elapsed time till latest delivery note
|
||||||
|
"""
|
||||||
|
so_elapsed_time = OrderedDict()
|
||||||
|
if data:
|
||||||
|
sales_orders = [x.sales_order for x in data]
|
||||||
|
|
||||||
|
so = qb.DocType("Sales Order")
|
||||||
|
soi = qb.DocType("Sales Order Item")
|
||||||
|
dn = qb.DocType("Delivery Note")
|
||||||
|
dni = qb.DocType("Delivery Note Item")
|
||||||
|
|
||||||
|
to_seconds = CustomFunction("TO_SECONDS", ["date"])
|
||||||
|
|
||||||
|
query = (
|
||||||
|
qb.from_(so)
|
||||||
|
.inner_join(soi)
|
||||||
|
.on(soi.parent == so.name)
|
||||||
|
.left_join(dni)
|
||||||
|
.on(dni.so_detail == soi.name)
|
||||||
|
.left_join(dn)
|
||||||
|
.on(dni.parent == dn.name)
|
||||||
|
.select(
|
||||||
|
so.name.as_("sales_order"),
|
||||||
|
soi.item_code.as_("so_item_code"),
|
||||||
|
(to_seconds(Max(dn.posting_date)) - to_seconds(so.transaction_date)).as_("elapsed_seconds"),
|
||||||
|
)
|
||||||
|
.where((so.name.isin(sales_orders)) & (dn.docstatus == 1))
|
||||||
|
.orderby(so.name, soi.name)
|
||||||
|
.groupby(soi.name)
|
||||||
|
)
|
||||||
|
dn_elapsed_time = query.run(as_dict=True)
|
||||||
|
|
||||||
|
for e in dn_elapsed_time:
|
||||||
|
key = (e.sales_order, e.so_item_code)
|
||||||
|
so_elapsed_time[key] = e.elapsed_seconds
|
||||||
|
|
||||||
|
return so_elapsed_time
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_data(data, so_elapsed_time, filters):
|
||||||
completed, pending = 0, 0
|
completed, pending = 0, 0
|
||||||
|
|
||||||
if filters.get("group_by_so"):
|
if filters.get("group_by_so"):
|
||||||
@ -115,6 +154,13 @@ def prepare_data(data, filters):
|
|||||||
row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
|
row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
|
||||||
|
|
||||||
row["delay"] = 0 if row["delay"] and row["delay"] < 0 else row["delay"]
|
row["delay"] = 0 if row["delay"] and row["delay"] < 0 else row["delay"]
|
||||||
|
|
||||||
|
row["time_taken_to_deliver"] = (
|
||||||
|
so_elapsed_time.get((row.sales_order, row.item_code))
|
||||||
|
if row["status"] in ("To Bill", "Completed")
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
if filters.get("group_by_so"):
|
if filters.get("group_by_so"):
|
||||||
so_name = row["sales_order"]
|
so_name = row["sales_order"]
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Delivery Note"]
|
|||||||
|
|
||||||
|
|
||||||
class TestSalesOrderAnalysis(FrappeTestCase):
|
class TestSalesOrderAnalysis(FrappeTestCase):
|
||||||
def create_sales_order(self, transaction_date):
|
def create_sales_order(self, transaction_date, do_not_save=False, do_not_submit=False):
|
||||||
item = create_item(item_code="_Test Excavator", is_stock_item=0)
|
item = create_item(item_code="_Test Excavator", is_stock_item=0)
|
||||||
so = make_sales_order(
|
so = make_sales_order(
|
||||||
transaction_date=transaction_date,
|
transaction_date=transaction_date,
|
||||||
@ -24,24 +24,30 @@ class TestSalesOrderAnalysis(FrappeTestCase):
|
|||||||
so.taxes_and_charges = ""
|
so.taxes_and_charges = ""
|
||||||
so.taxes = ""
|
so.taxes = ""
|
||||||
so.items[0].delivery_date = add_days(transaction_date, 15)
|
so.items[0].delivery_date = add_days(transaction_date, 15)
|
||||||
|
if not do_not_save:
|
||||||
so.save()
|
so.save()
|
||||||
|
if not do_not_submit:
|
||||||
so.submit()
|
so.submit()
|
||||||
return item, so
|
return item, so
|
||||||
|
|
||||||
def create_sales_invoice(self, so):
|
def create_sales_invoice(self, so, do_not_save=False, do_not_submit=False):
|
||||||
sinv = make_sales_invoice(so.name)
|
sinv = make_sales_invoice(so.name)
|
||||||
sinv.posting_date = so.transaction_date
|
sinv.posting_date = so.transaction_date
|
||||||
sinv.taxes_and_charges = ""
|
sinv.taxes_and_charges = ""
|
||||||
sinv.taxes = ""
|
sinv.taxes = ""
|
||||||
sinv.insert()
|
if not do_not_save:
|
||||||
|
sinv.save()
|
||||||
|
if not do_not_submit:
|
||||||
sinv.submit()
|
sinv.submit()
|
||||||
return sinv
|
return sinv
|
||||||
|
|
||||||
def create_delivery_note(self, so):
|
def create_delivery_note(self, so, do_not_save=False, do_not_submit=False):
|
||||||
dn = make_delivery_note(so.name)
|
dn = make_delivery_note(so.name)
|
||||||
dn.set_posting_time = True
|
dn.set_posting_time = True
|
||||||
dn.posting_date = add_days(so.transaction_date, 1)
|
dn.posting_date = add_days(so.transaction_date, 1)
|
||||||
|
if not do_not_save:
|
||||||
dn.save()
|
dn.save()
|
||||||
|
if not do_not_submit:
|
||||||
dn.submit()
|
dn.submit()
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
@ -164,3 +170,85 @@ class TestSalesOrderAnalysis(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
# SO's from first 4 test cases should be in output
|
# SO's from first 4 test cases should be in output
|
||||||
self.assertEqual(len(data), 4)
|
self.assertEqual(len(data), 4)
|
||||||
|
|
||||||
|
def test_06_so_pending_delivery_with_multiple_delivery_notes(self):
|
||||||
|
transaction_date = "2021-06-01"
|
||||||
|
item, so = self.create_sales_order(transaction_date)
|
||||||
|
|
||||||
|
# bill 2 items
|
||||||
|
sinv1 = self.create_sales_invoice(so, do_not_save=True)
|
||||||
|
sinv1.items[0].qty = 2
|
||||||
|
sinv1 = sinv1.save().submit()
|
||||||
|
# deliver 2 items
|
||||||
|
dn1 = self.create_delivery_note(so, do_not_save=True)
|
||||||
|
dn1.items[0].qty = 2
|
||||||
|
dn1 = dn1.save().submit()
|
||||||
|
|
||||||
|
# bill 2 items
|
||||||
|
sinv2 = self.create_sales_invoice(so, do_not_save=True)
|
||||||
|
sinv2.items[0].qty = 2
|
||||||
|
sinv2 = sinv2.save().submit()
|
||||||
|
# deliver 1 item
|
||||||
|
dn2 = self.create_delivery_note(so, do_not_save=True)
|
||||||
|
dn2.items[0].qty = 1
|
||||||
|
dn2 = dn2.save().submit()
|
||||||
|
|
||||||
|
columns, data, message, chart = execute(
|
||||||
|
{
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-06-01",
|
||||||
|
"to_date": "2021-06-30",
|
||||||
|
"sales_order": [so.name],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expected_value = {
|
||||||
|
"status": "To Deliver and Bill",
|
||||||
|
"sales_order": so.name,
|
||||||
|
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
|
||||||
|
"qty": 10,
|
||||||
|
"delivered_qty": 3,
|
||||||
|
"pending_qty": 7,
|
||||||
|
"qty_to_bill": 6,
|
||||||
|
"billed_qty": 4,
|
||||||
|
"time_taken_to_deliver": 0,
|
||||||
|
}
|
||||||
|
self.assertEqual(len(data), 1)
|
||||||
|
for key, val in expected_value.items():
|
||||||
|
with self.subTest(key=key, val=val):
|
||||||
|
self.assertEqual(data[0][key], val)
|
||||||
|
|
||||||
|
def test_07_so_delivered_with_multiple_delivery_notes(self):
|
||||||
|
transaction_date = "2021-06-01"
|
||||||
|
item, so = self.create_sales_order(transaction_date)
|
||||||
|
|
||||||
|
dn1 = self.create_delivery_note(so, do_not_save=True)
|
||||||
|
dn1.items[0].qty = 5
|
||||||
|
dn1 = dn1.save().submit()
|
||||||
|
|
||||||
|
dn2 = self.create_delivery_note(so, do_not_save=True)
|
||||||
|
dn2.items[0].qty = 5
|
||||||
|
dn2 = dn2.save().submit()
|
||||||
|
|
||||||
|
columns, data, message, chart = execute(
|
||||||
|
{
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-06-01",
|
||||||
|
"to_date": "2021-06-30",
|
||||||
|
"sales_order": [so.name],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expected_value = {
|
||||||
|
"status": "To Bill",
|
||||||
|
"sales_order": so.name,
|
||||||
|
"delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date),
|
||||||
|
"qty": 10,
|
||||||
|
"delivered_qty": 10,
|
||||||
|
"pending_qty": 0,
|
||||||
|
"qty_to_bill": 10,
|
||||||
|
"billed_qty": 0,
|
||||||
|
"time_taken_to_deliver": 86400,
|
||||||
|
}
|
||||||
|
self.assertEqual(len(data), 1)
|
||||||
|
for key, val in expected_value.items():
|
||||||
|
with self.subTest(key=key, val=val):
|
||||||
|
self.assertEqual(data[0][key], val)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user