Skip to content

Commit

Permalink
Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount
Browse files Browse the repository at this point in the history
  • Loading branch information
s-aga-r authored Jan 31, 2022
2 parents fedeb2a + 5ac11a4 commit c415501
Show file tree
Hide file tree
Showing 29 changed files with 272 additions and 146 deletions.
20 changes: 20 additions & 0 deletions erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,26 @@ def test_pricing_rule_for_transaction(self):
for doc in [si, si1]:
doc.delete()

def test_multiple_pricing_rules_with_min_qty(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")

si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
item = si.items[0]
item.stock_qty = 1
si.save()
self.assertFalse(item.discount_percentage)
item.qty = 5
item.stock_qty = 5
si.save()
self.assertEqual(item.discount_percentage, 30)
si.delete()

frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")

test_dependencies = ["Campaign"]

def make_pricing_rule(**args):
Expand Down
2 changes: 1 addition & 1 deletion erpnext/accounts/doctype/pricing_rule/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def sorted_by_priority(pricing_rules, args, doc=None):
for key in sorted(pricing_rule_dict):
pricing_rules_list.extend(pricing_rule_dict.get(key))

return pricing_rules_list or pricing_rules
return pricing_rules_list

def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
Expand Down
3 changes: 2 additions & 1 deletion erpnext/accounts/doctype/shipping_rule/shipping_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def apply(self, doc):
if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)

self.add_shipping_rule_to_tax_table(doc, shipping_amount)
if shipping_amount:
self.add_shipping_rule_to_tax_table(doc, shipping_amount)

def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
Expand Down
16 changes: 8 additions & 8 deletions erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ def validate_serialized_batch(self):
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))

def clean_serial_nos(self):
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string

for row in self.get("items"):
if hasattr(row, "serial_no") and row.serial_no:
# replace commas by linefeed
row.serial_no = row.serial_no.replace(",", "\n")

# strip preceeding and succeeding spaces for each SN
# (SN could have valid spaces in between e.g. SN - 123 - 2021)
serial_no_list = row.serial_no.split("\n")
serial_no_list = [sn.strip() for sn in serial_no_list]
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)

row.serial_no = "\n".join(serial_no_list)
for row in self.get('packed_items') or []:
if hasattr(row, "serial_no") and row.serial_no:
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)

def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
Expand Down
11 changes: 2 additions & 9 deletions erpnext/manufacturing/doctype/bom/bom.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"inspection_required",
"quality_inspection_template",
"column_break_31",
"bom_level",
"section_break_33",
"items",
"scrap_section",
Expand Down Expand Up @@ -522,13 +521,6 @@
"fieldname": "column_break_31",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "bom_level",
"fieldtype": "Int",
"label": "BOM Level",
"read_only": 1
},
{
"fieldname": "section_break_33",
"fieldtype": "Section Break",
Expand All @@ -540,7 +532,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2021-11-18 13:04:16.271975",
"modified": "2022-01-30 21:27:54.727298",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
Expand Down Expand Up @@ -577,5 +569,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
17 changes: 0 additions & 17 deletions erpnext/manufacturing/doctype/bom/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ def validate(self):
self.update_stock_qty()
self.validate_scrap_items()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
self.set_bom_level()


def get_context(self, context):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
Expand Down Expand Up @@ -704,7 +702,6 @@ def validate_operations(self):
if not d.batch_size or d.batch_size <= 0:
d.batch_size = 1


def validate_scrap_items(self):
for item in self.scrap_items:
msg = ""
Expand Down Expand Up @@ -735,20 +732,6 @@ def get_tree_representation(self) -> BOMTree:
"""Get a complete tree representation preserving order of child items."""
return BOMTree(self.name)

def set_bom_level(self, update=False):
levels = []

self.bom_level = 0
for row in self.items:
if row.bom_no:
levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0)

if levels:
self.bom_level = max(levels) + 1

if update:
self.db_set("bom_level", self.bom_level)

def get_bom_item_rate(args, bom_doc):
if bom_doc.rm_cost_as_per == 'Valuation Rate':
rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
Expand Down
11 changes: 5 additions & 6 deletions erpnext/manufacturing/doctype/production_plan/production_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,11 @@ def get_sub_assembly_items(self, manufacturing_type=None):
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)

def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
bom_data = sorted(bom_data, key = lambda i: i.bom_level)
self.sub_assembly_items.sort(key= lambda d: d.bom_level, reverse=True)
for idx, row in enumerate(self.sub_assembly_items, start=1):
row.idx = idx

def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
Expand Down Expand Up @@ -1005,9 +1007,6 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level")
if d.value else 0)

stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
bom_data.append(frappe._dict({
'parent_item_code': parent_item_code,
Expand All @@ -1018,7 +1017,7 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
'uom': d.stock_uom,
'bom_no': d.value,
'is_sub_contracted_item': d.is_sub_contracted_item,
'bom_level': bom_level,
'bom_level': indent,
'indent': indent,
'stock_qty': stock_qty
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,45 @@ def test_get_sales_order_with_variant(self):

frappe.db.rollback()

def test_subassmebly_sorting(self):
""" Test subassembly sorting in case of multiple items with nested BOMs"""
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom

prefix = "_TestLevel_"
boms = {
"Assembly": {
"SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
"SubAssembly2": {"ChildPart3": {}},
"SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}},
"ChildPart5": {},
"ChildPart6": {},
"SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}},
},
"MegaDeepAssy": {
"SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},},
# ^ assert that this is
# first item in subassy table
}
}
create_nested_bom(boms, prefix=prefix)

items = [prefix + item_code for item_code in boms.keys()]
plan = create_production_plan(item_code=items[0], do_not_save=True)
plan.append("po_items", {
'use_multi_level_bom': 1,
'item_code': items[1],
'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'),
'planned_qty': 1,
'planned_start_date': now_datetime()
})
plan.get_sub_assembly_items()

bom_level_order = [d.bom_level for d in plan.sub_assembly_items]
self.assertEqual(bom_level_order, sorted(bom_level_order, reverse=True))
# lowest most level of subassembly should be first
self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item)


def create_production_plan(**args):
args = frappe._dict(args)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@
},
{
"columns": 1,
"fetch_from": "bom_no.bom_level",
"fieldname": "bom_level",
"fieldtype": "Int",
"in_list_view": 1,
Expand Down Expand Up @@ -189,7 +188,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-06-28 20:10:56.296410",
"modified": "2022-01-30 21:31:10.527559",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",
Expand All @@ -198,5 +197,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
2 changes: 2 additions & 0 deletions erpnext/manufacturing/doctype/work_order/work_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
from erpnext.stock.doctype.serial_no.serial_no import (
auto_make_serial_nos,
clean_serial_no_string,
get_auto_serial_nos,
get_serial_nos,
)
Expand Down Expand Up @@ -358,6 +359,7 @@ def delete_auto_created_batch_and_serial_no(self):
frappe.delete_doc("Batch", row.name)

def make_serial_nos(self, args):
self.serial_no = clean_serial_no_string(self.serial_no)
serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
if serial_no_series:
self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
Expand Down
5 changes: 2 additions & 3 deletions erpnext/manufacturing/report/bom_explorer/bom_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ def get_exploded_items(bom, data, indent=0, qty=1):
'item_code': item.item_code,
'item_name': item.item_name,
'indent': indent,
'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level")
if item.bom_no else ""),
'bom_level': indent,
'bom': item.bom_no,
'qty': item.qty * qty,
'uom': item.uom,
Expand Down Expand Up @@ -73,7 +72,7 @@ def get_columns():
},
{
"label": "BOM Level",
"fieldtype": "Data",
"fieldtype": "Int",
"fieldname": "bom_level",
"width": 100
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ frappe.query_reports["Cost of Poor Quality Report"] = {
fieldname:"from_date",
fieldtype: "Datetime",
default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
reqd: 1
},
{
label: __("To Date"),
fieldname:"to_date",
fieldtype: "Datetime",
default: frappe.datetime.now_datetime(),
reqd: 1,
},
{
label: __("Job Card"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,65 @@

import frappe
from frappe import _
from frappe.utils import flt


def execute(filters=None):
columns, data = [], []
return get_columns(filters), get_data(filters)

columns = get_columns(filters)
data = get_data(filters)

return columns, data

def get_data(report_filters):
data = []
operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
if operations:
operations = [d.name for d in operations]
fields = ["production_item as item_code", "item_name", "work_order", "operation",
"workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"]
if report_filters.get('operation'):
operations = [report_filters.get('operation')]
else:
operations = [d.name for d in operations]

filters = get_filters(report_filters, operations)
job_card = frappe.qb.DocType("Job Card")

job_cards = frappe.get_all("Job Card", fields = fields,
filters = filters)
operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost')
item_code = (job_card.production_item).as_('item_code')

for row in job_cards:
row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0)
data.append(row)
query = (frappe.qb
.from_(job_card)
.select(job_card.name, job_card.work_order, item_code, job_card.item_name,
job_card.operation, job_card.serial_no, job_card.batch_no,
job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate,
operating_cost)
.where(
(job_card.docstatus == 1)
& (job_card.is_corrective_job_card == 1))
.groupby(job_card.name)
)

query = append_filters(query, report_filters, operations, job_card)
data = query.run(as_dict=True)
return data

def get_filters(report_filters, operations):
filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1}
for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]:
def append_filters(query, report_filters, operations, job_card):
"""Append optional filters to query builder. """

for field in ("name", "work_order", "operation", "workstation",
"company", "serial_no", "batch_no", "production_item"):
if report_filters.get(field):
if field != 'serial_no':
filters[field] = report_filters.get(field)
if field == 'serial_no':
query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field))))
elif field == 'operation':
query = query.where(job_card[field].isin(operations))
else:
filters[field] = ('like', '% {} %'.format(report_filters.get(field)))
query = query.where(job_card[field] == report_filters.get(field))

if report_filters.get('from_date') or report_filters.get('to_date'):
job_card_time_log = frappe.qb.DocType("Job Card Time Log")

query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent)
if report_filters.get('from_date'):
query = query.where(job_card_time_log.from_time >= report_filters.get('from_date'))
if report_filters.get('to_date'):
query = query.where(job_card_time_log.to_time <= report_filters.get('to_date'))

return filters
return query

def get_columns(filters):
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_production_plan_item_details(filters, data, order_details):
"qty": row.planned_qty,
"document_type": "Work Order",
"document_name": work_order or "",
"bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"),
"bom_level": 0,
"produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
"pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0))
})
Expand Down
Loading

0 comments on commit c415501

Please sign in to comment.