Merge branch 'version-13-hotfix' into item-tax-templates
This commit is contained in:
commit
c99eaf106e
12
.git-blame-ignore-revs
Normal file
12
.git-blame-ignore-revs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Since version 2.23 (released in August 2019), git-blame has a feature
|
||||||
|
# to ignore or bypass certain commits.
|
||||||
|
#
|
||||||
|
# This file contains a list of commits that are not likely what you
|
||||||
|
# are looking for in a blame, such as mass reformatting or renaming.
|
||||||
|
# You can set this file as a default ignore file for blame by running
|
||||||
|
# the following command.
|
||||||
|
#
|
||||||
|
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
|
||||||
|
# This commit just changes spaces to tabs for indentation in some files
|
||||||
|
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||||
@ -225,7 +225,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
def validate_date_with_fiscal_year(self):
|
def validate_date_with_fiscal_year(self):
|
||||||
if self.meta.get_field("fiscal_year"):
|
if self.meta.get_field("fiscal_year"):
|
||||||
date_field = ""
|
date_field = None
|
||||||
if self.meta.get_field("posting_date"):
|
if self.meta.get_field("posting_date"):
|
||||||
date_field = "posting_date"
|
date_field = "posting_date"
|
||||||
elif self.meta.get_field("transaction_date"):
|
elif self.meta.get_field("transaction_date"):
|
||||||
@ -1449,6 +1449,7 @@ def validate_and_delete_children(parent, data):
|
|||||||
for d in deleted_children:
|
for d in deleted_children:
|
||||||
update_bin_on_delete(d, parent.doctype)
|
update_bin_on_delete(d, parent.doctype)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||||
def check_doc_permissions(doc, perm_type='create'):
|
def check_doc_permissions(doc, perm_type='create'):
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
import json
|
||||||
import frappe, erpnext
|
|
||||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
|
||||||
from frappe import _
|
|
||||||
import frappe.defaults
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
|
||||||
|
import frappe
|
||||||
|
import frappe.defaults
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
|
||||||
|
|
||||||
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
||||||
|
from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.stock.stock_ledger import get_valuation_rate
|
|
||||||
from erpnext.stock import get_warehouse_account_map
|
from erpnext.stock import get_warehouse_account_map
|
||||||
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
|
|
||||||
class QualityInspectionRequiredError(frappe.ValidationError): pass
|
class QualityInspectionRequiredError(frappe.ValidationError): pass
|
||||||
class QualityInspectionRejectedError(frappe.ValidationError): pass
|
class QualityInspectionRejectedError(frappe.ValidationError): pass
|
||||||
@ -189,7 +193,6 @@ class StockController(AccountsController):
|
|||||||
if hasattr(self, "items"):
|
if hasattr(self, "items"):
|
||||||
item_doclist = self.get("items")
|
item_doclist = self.get("items")
|
||||||
elif self.doctype == "Stock Reconciliation":
|
elif self.doctype == "Stock Reconciliation":
|
||||||
import json
|
|
||||||
item_doclist = []
|
item_doclist = []
|
||||||
data = json.loads(self.reconciliation_json)
|
data = json.loads(self.reconciliation_json)
|
||||||
for row in data[data.index(self.head_row)+1:]:
|
for row in data[data.index(self.head_row)+1:]:
|
||||||
@ -319,7 +322,7 @@ class StockController(AccountsController):
|
|||||||
return serialized_items
|
return serialized_items
|
||||||
|
|
||||||
def validate_warehouse(self):
|
def validate_warehouse(self):
|
||||||
from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
|
from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
|
||||||
|
|
||||||
warehouses = list(set([d.warehouse for d in
|
warehouses = list(set([d.warehouse for d in
|
||||||
self.get("items") if getattr(d, "warehouse", None)]))
|
self.get("items") if getattr(d, "warehouse", None)]))
|
||||||
@ -498,6 +501,39 @@ class StockController(AccountsController):
|
|||||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||||
self.company, self.doctype, self.name)
|
self.company, self.doctype, self.name)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_quality_inspections(doctype, docname, items):
|
||||||
|
if isinstance(items, str):
|
||||||
|
items = json.loads(items)
|
||||||
|
|
||||||
|
inspections = []
|
||||||
|
for item in items:
|
||||||
|
if flt(item.get("sample_size")) > flt(item.get("qty")):
|
||||||
|
frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
|
||||||
|
item_name=item.get("item_name"),
|
||||||
|
sample_size=item.get("sample_size"),
|
||||||
|
accepted_quantity=item.get("qty")
|
||||||
|
))
|
||||||
|
|
||||||
|
quality_inspection = frappe.get_doc({
|
||||||
|
"doctype": "Quality Inspection",
|
||||||
|
"inspection_type": "Incoming",
|
||||||
|
"inspected_by": frappe.session.user,
|
||||||
|
"reference_type": doctype,
|
||||||
|
"reference_name": docname,
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
"description": item.get("description"),
|
||||||
|
"sample_size": flt(item.get("sample_size")),
|
||||||
|
"item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
|
||||||
|
"batch_no": item.get("batch_no")
|
||||||
|
}).insert()
|
||||||
|
quality_inspection.save()
|
||||||
|
inspections.append(quality_inspection.name)
|
||||||
|
|
||||||
|
return inspections
|
||||||
|
|
||||||
|
|
||||||
def is_reposting_pending():
|
def is_reposting_pending():
|
||||||
return frappe.db.exists("Repost Item Valuation",
|
return frappe.db.exists("Repost Item Valuation",
|
||||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||||
|
|||||||
@ -211,16 +211,27 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get_items: function(frm) {
|
get_items: function (frm) {
|
||||||
|
frm.clear_table('prod_plan_references');
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "get_items",
|
method: "get_items",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function() {
|
callback: function () {
|
||||||
refresh_field('po_items');
|
refresh_field('po_items');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
combine_items: function (frm) {
|
||||||
|
frm.clear_table('prod_plan_references');
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "get_items",
|
||||||
|
freeze: true,
|
||||||
|
doc: frm.doc,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
get_items_for_mr: function(frm) {
|
get_items_for_mr: function(frm) {
|
||||||
if (!frm.doc.for_warehouse) {
|
if (!frm.doc.for_warehouse) {
|
||||||
|
|||||||
@ -28,7 +28,10 @@
|
|||||||
"material_requests",
|
"material_requests",
|
||||||
"select_items_to_manufacture_section",
|
"select_items_to_manufacture_section",
|
||||||
"get_items",
|
"get_items",
|
||||||
|
"combine_items",
|
||||||
"po_items",
|
"po_items",
|
||||||
|
"section_break_25",
|
||||||
|
"prod_plan_references",
|
||||||
"material_request_planning",
|
"material_request_planning",
|
||||||
"include_non_stock_items",
|
"include_non_stock_items",
|
||||||
"include_subcontracted_items",
|
"include_subcontracted_items",
|
||||||
@ -316,13 +319,31 @@
|
|||||||
"fieldname": "include_safety_stock",
|
"fieldname": "include_safety_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Include Safety Stock in Required Qty Calculation"
|
"label": "Include Safety Stock in Required Qty Calculation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
||||||
|
"fieldname": "combine_items",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Consolidate Items"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_25",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "prod_plan_references",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Production Plan Item Reference",
|
||||||
|
"options": "Production Plan Item Reference"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-08 11:17:25.470147",
|
"modified": "2021-05-24 16:59:03.643211",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
|||||||
@ -96,8 +96,10 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items(self):
|
def get_items(self):
|
||||||
|
self.set('po_items', [])
|
||||||
if self.get_items_from == "Sales Order":
|
if self.get_items_from == "Sales Order":
|
||||||
self.get_so_items()
|
self.get_so_items()
|
||||||
|
|
||||||
elif self.get_items_from == "Material Request":
|
elif self.get_items_from == "Material Request":
|
||||||
self.get_mr_items()
|
self.get_mr_items()
|
||||||
|
|
||||||
@ -165,9 +167,31 @@ class ProductionPlan(Document):
|
|||||||
self.calculate_total_planned_qty()
|
self.calculate_total_planned_qty()
|
||||||
|
|
||||||
def add_items(self, items):
|
def add_items(self, items):
|
||||||
self.set('po_items', [])
|
refs = {}
|
||||||
for data in items:
|
for data in items:
|
||||||
item_details = get_item_details(data.item_code)
|
item_details = get_item_details(data.item_code)
|
||||||
|
if self.combine_items:
|
||||||
|
if item_details.bom_no in refs:
|
||||||
|
refs[item_details.bom_no]['so_details'].append({
|
||||||
|
'sales_order': data.parent,
|
||||||
|
'sales_order_item': data.name,
|
||||||
|
'qty': data.pending_qty
|
||||||
|
})
|
||||||
|
refs[item_details.bom_no]['qty'] += data.pending_qty
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
refs[item_details.bom_no] = {
|
||||||
|
'qty': data.pending_qty,
|
||||||
|
'po_item_ref': data.name,
|
||||||
|
'so_details': []
|
||||||
|
}
|
||||||
|
refs[item_details.bom_no]['so_details'].append({
|
||||||
|
'sales_order': data.parent,
|
||||||
|
'sales_order_item': data.name,
|
||||||
|
'qty': data.pending_qty
|
||||||
|
})
|
||||||
|
|
||||||
pi = self.append('po_items', {
|
pi = self.append('po_items', {
|
||||||
'include_exploded_items': 1,
|
'include_exploded_items': 1,
|
||||||
'warehouse': data.warehouse,
|
'warehouse': data.warehouse,
|
||||||
@ -191,6 +215,23 @@ class ProductionPlan(Document):
|
|||||||
pi.material_request_item = data.name
|
pi.material_request_item = data.name
|
||||||
pi.description = data.description
|
pi.description = data.description
|
||||||
|
|
||||||
|
if refs:
|
||||||
|
for po_item in self.po_items:
|
||||||
|
po_item.planned_qty = refs[po_item.bom_no]['qty']
|
||||||
|
po_item.pending_qty = refs[po_item.bom_no]['qty']
|
||||||
|
po_item.sales_order = ''
|
||||||
|
self.add_pp_ref(refs)
|
||||||
|
|
||||||
|
def add_pp_ref(self, refs):
|
||||||
|
for bom_no in refs:
|
||||||
|
for so_detail in refs[bom_no]['so_details']:
|
||||||
|
self.append('prod_plan_references', {
|
||||||
|
'item_reference': refs[bom_no]['po_item_ref'],
|
||||||
|
'sales_order': so_detail['sales_order'],
|
||||||
|
'sales_order_item': so_detail['sales_order_item'],
|
||||||
|
'qty': so_detail['qty']
|
||||||
|
})
|
||||||
|
|
||||||
def calculate_total_planned_qty(self):
|
def calculate_total_planned_qty(self):
|
||||||
self.total_planned_qty = 0
|
self.total_planned_qty = 0
|
||||||
for d in self.po_items:
|
for d in self.po_items:
|
||||||
|
|||||||
@ -100,7 +100,7 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
def test_production_plan_sales_orders(self):
|
def test_production_plan_sales_orders(self):
|
||||||
item = 'Test Production Item 1'
|
item = 'Test Production Item 1'
|
||||||
so = make_sales_order(item_code=item, qty=5)
|
so = make_sales_order(item_code=item, qty=1)
|
||||||
sales_order = so.name
|
sales_order = so.name
|
||||||
sales_order_item = so.items[0].name
|
sales_order_item = so.items[0].name
|
||||||
|
|
||||||
@ -124,8 +124,8 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
wo_doc = frappe.get_doc('Work Order', work_order)
|
wo_doc = frappe.get_doc('Work Order', work_order)
|
||||||
wo_doc.update({
|
wo_doc.update({
|
||||||
'wip_warehouse': '_Test Warehouse 1 - _TC',
|
'wip_warehouse': 'Work In Progress - _TC',
|
||||||
'fg_warehouse': '_Test Warehouse - _TC'
|
'fg_warehouse': 'Finished Goods - _TC'
|
||||||
})
|
})
|
||||||
wo_doc.submit()
|
wo_doc.submit()
|
||||||
|
|
||||||
@ -145,6 +145,58 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sales_orders, [])
|
self.assertEqual(sales_orders, [])
|
||||||
|
|
||||||
|
def test_production_plan_combine_items(self):
|
||||||
|
item = 'Test Production Item 1'
|
||||||
|
so = make_sales_order(item_code=item, qty=1)
|
||||||
|
|
||||||
|
pln = frappe.new_doc('Production Plan')
|
||||||
|
pln.company = so.company
|
||||||
|
pln.get_items_from = 'Sales Order'
|
||||||
|
pln.append('sales_orders', {
|
||||||
|
'sales_order': so.name,
|
||||||
|
'sales_order_date': so.transaction_date,
|
||||||
|
'customer': so.customer,
|
||||||
|
'grand_total': so.grand_total
|
||||||
|
})
|
||||||
|
so = make_sales_order(item_code=item, qty=2)
|
||||||
|
pln.append('sales_orders', {
|
||||||
|
'sales_order': so.name,
|
||||||
|
'sales_order_date': so.transaction_date,
|
||||||
|
'customer': so.customer,
|
||||||
|
'grand_total': so.grand_total
|
||||||
|
})
|
||||||
|
pln.combine_items = 1
|
||||||
|
pln.get_items()
|
||||||
|
pln.submit()
|
||||||
|
|
||||||
|
self.assertTrue(pln.po_items[0].planned_qty, 3)
|
||||||
|
|
||||||
|
pln.make_work_order()
|
||||||
|
work_order = frappe.db.get_value('Work Order', {
|
||||||
|
'production_plan_item': pln.po_items[0].name,
|
||||||
|
'production_plan': pln.name
|
||||||
|
}, 'name')
|
||||||
|
|
||||||
|
wo_doc = frappe.get_doc('Work Order', work_order)
|
||||||
|
wo_doc.update({
|
||||||
|
'wip_warehouse': 'Work In Progress - _TC',
|
||||||
|
})
|
||||||
|
|
||||||
|
wo_doc.submit()
|
||||||
|
so_items = []
|
||||||
|
for plan_reference in pln.prod_plan_references:
|
||||||
|
so_items.append(plan_reference.sales_order_item)
|
||||||
|
so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty')
|
||||||
|
self.assertEqual(so_wo_qty, plan_reference.qty)
|
||||||
|
|
||||||
|
wo_doc.cancel()
|
||||||
|
for so_item in so_items:
|
||||||
|
so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
|
||||||
|
self.assertEqual(so_wo_qty, 0.0)
|
||||||
|
|
||||||
|
latest_plan = frappe.get_doc('Production Plan', pln.name)
|
||||||
|
latest_plan.cancel()
|
||||||
|
|
||||||
def test_pp_to_mr_customer_provided(self):
|
def test_pp_to_mr_customer_provided(self):
|
||||||
#Material Request from Production Plan for Customer Provided
|
#Material Request from Production Plan for Customer Provided
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
|
|||||||
@ -1,792 +1,229 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:27:49",
|
"creation": "2013-02-22 01:27:49",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"include_exploded_items",
|
||||||
|
"item_code",
|
||||||
|
"bom_no",
|
||||||
|
"planned_qty",
|
||||||
|
"column_break_6",
|
||||||
|
"make_work_order_for_sub_assembly_items",
|
||||||
|
"warehouse",
|
||||||
|
"planned_start_date",
|
||||||
|
"section_break_9",
|
||||||
|
"pending_qty",
|
||||||
|
"ordered_qty",
|
||||||
|
"produced_qty",
|
||||||
|
"column_break_17",
|
||||||
|
"description",
|
||||||
|
"stock_uom",
|
||||||
|
"reference_section",
|
||||||
|
"sales_order",
|
||||||
|
"sales_order_item",
|
||||||
|
"column_break_19",
|
||||||
|
"material_request",
|
||||||
|
"material_request_item",
|
||||||
|
"product_bundle_item",
|
||||||
|
"item_reference"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fetch_if_empty": 0,
|
"default": "0",
|
||||||
"fieldname": "include_exploded_items",
|
"fieldname": "include_exploded_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Include Exploded Items"
|
||||||
"label": "Include Exploded Items",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Code",
|
"label": "Item Code",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "item_code",
|
"oldfieldname": "item_code",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "150px",
|
"print_width": "150px",
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "bom_no",
|
"fieldname": "bom_no",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "BOM No",
|
"label": "BOM No",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "bom_no",
|
"oldfieldname": "bom_no",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "BOM",
|
"options": "BOM",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "planned_qty",
|
"fieldname": "planned_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Planned Qty",
|
"label": "Planned Qty",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "planned_qty",
|
"oldfieldname": "planned_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_6",
|
"fieldname": "column_break_6",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
|
"description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "make_work_order_for_sub_assembly_items",
|
"fieldname": "make_work_order_for_sub_assembly_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Make Work Order for Sub Assembly Items"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Make Work Order for Sub Assembly Items",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "",
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "For Warehouse",
|
"label": "For Warehouse",
|
||||||
"length": 0,
|
"options": "Warehouse"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Warehouse",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "planned_start_date",
|
"fieldname": "planned_start_date",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Planned Start Date",
|
"label": "Planned Start Date",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "section_break_9",
|
"fieldname": "section_break_9",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Quantity and Description"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Quantity and Description",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "pending_qty",
|
"fieldname": "pending_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Pending Qty",
|
"label": "Pending Qty",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "prevdoc_reqd_qty",
|
"oldfieldname": "prevdoc_reqd_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "ordered_qty",
|
"fieldname": "ordered_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Ordered Qty",
|
"label": "Ordered Qty",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "produced_qty",
|
"fieldname": "produced_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Produced Qty",
|
"label": "Produced Qty",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_17",
|
"fieldname": "column_break_17",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "200px",
|
"print_width": "200px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "UOM",
|
"label": "UOM",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "stock_uom",
|
"oldfieldname": "stock_uom",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "UOM",
|
"options": "UOM",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "80px",
|
"print_width": "80px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "80px"
|
"width": "80px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "reference_section",
|
"fieldname": "reference_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Reference"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Reference",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "sales_order",
|
"fieldname": "sales_order",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Sales Order",
|
"label": "Sales Order",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "source_docname",
|
"oldfieldname": "source_docname",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "Sales Order",
|
"options": "Sales Order",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "sales_order_item",
|
"fieldname": "sales_order_item",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Sales Order Item",
|
"label": "Sales Order Item",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
"print_hide": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_19",
|
"fieldname": "column_break_19",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "material_request",
|
"fieldname": "material_request",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Material Request",
|
"label": "Material Request",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Material Request",
|
"options": "Material Request",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "material_request_item",
|
"fieldname": "material_request_item",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
"label": "material_request_item"
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "material_request_item",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "product_bundle_item",
|
"fieldname": "product_bundle_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Product Bundle Item",
|
"label": "Product Bundle Item",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
},
|
||||||
"remember_last_selected_value": 0,
|
{
|
||||||
"report_hide": 0,
|
"fieldname": "item_reference",
|
||||||
"reqd": 0,
|
"fieldtype": "Data",
|
||||||
"search_index": 0,
|
"hidden": 1,
|
||||||
"set_only_once": 0,
|
"label": "Item Reference"
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2019-04-08 23:09:57.199423",
|
"modified": "2021-04-28 19:14:57.772123",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Item",
|
"name": "Production Plan Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "ASC"
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_order": "ASC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-04-22 10:32:58.896330",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"item_reference",
|
||||||
|
"sales_order",
|
||||||
|
"sales_order_item",
|
||||||
|
"qty"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "sales_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Sales Order Reference",
|
||||||
|
"options": "Sales Order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_order_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Sales Order Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_reference",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Item Reference"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-07 17:03:49.707487",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Production Plan Item Reference",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class ProductionPlanItemReference(Document):
|
||||||
|
pass
|
||||||
@ -241,7 +241,11 @@ class WorkOrder(Document):
|
|||||||
if not self.fg_warehouse:
|
if not self.fg_warehouse:
|
||||||
frappe.throw(_("For Warehouse is required before Submit"))
|
frappe.throw(_("For Warehouse is required before Submit"))
|
||||||
|
|
||||||
self.update_work_order_qty_in_so()
|
if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
|
||||||
|
self.update_work_order_qty_in_combined_so()
|
||||||
|
else:
|
||||||
|
self.update_work_order_qty_in_so()
|
||||||
|
|
||||||
self.update_reserved_qty_for_production()
|
self.update_reserved_qty_for_production()
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
@ -250,9 +254,13 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.validate_cancel()
|
self.validate_cancel()
|
||||||
|
|
||||||
frappe.db.set(self,'status', 'Cancelled')
|
frappe.db.set(self,'status', 'Cancelled')
|
||||||
self.update_work_order_qty_in_so()
|
|
||||||
|
if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
|
||||||
|
self.update_work_order_qty_in_combined_so()
|
||||||
|
else:
|
||||||
|
self.update_work_order_qty_in_so()
|
||||||
|
|
||||||
self.delete_job_card()
|
self.delete_job_card()
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
@ -358,6 +366,27 @@ class WorkOrder(Document):
|
|||||||
frappe.db.set_value('Sales Order Item',
|
frappe.db.set_value('Sales Order Item',
|
||||||
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
|
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
|
||||||
|
|
||||||
|
def update_work_order_qty_in_combined_so(self):
|
||||||
|
total_bundle_qty = 1
|
||||||
|
if self.product_bundle_item:
|
||||||
|
total_bundle_qty = frappe.db.sql(""" select sum(qty) from
|
||||||
|
`tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
|
||||||
|
|
||||||
|
if not total_bundle_qty:
|
||||||
|
# product bundle is 0 (product bundle allows 0 qty for items)
|
||||||
|
total_bundle_qty = 1
|
||||||
|
|
||||||
|
prod_plan = frappe.get_doc('Production Plan', self.production_plan)
|
||||||
|
item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
|
||||||
|
|
||||||
|
for plan_reference in prod_plan.prod_plan_references:
|
||||||
|
work_order_qty = 0.0
|
||||||
|
if plan_reference.item_reference == item_reference:
|
||||||
|
if self.docstatus == 1:
|
||||||
|
work_order_qty = flt(plan_reference.qty) / total_bundle_qty
|
||||||
|
frappe.db.set_value('Sales Order Item',
|
||||||
|
plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
|
||||||
|
|
||||||
def update_completed_qty_in_material_request(self):
|
def update_completed_qty_in_material_request(self):
|
||||||
if self.material_request:
|
if self.material_request:
|
||||||
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
||||||
|
|||||||
@ -261,11 +261,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
|
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var me = this;
|
|
||||||
var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
|
const me = this;
|
||||||
|
if (!this.frm.is_new() && this.frm.doc.docstatus === 0) {
|
||||||
|
this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
|
||||||
|
me.make_quality_inspection();
|
||||||
|
}, __("Create"));
|
||||||
|
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
|
||||||
? "Incoming" : "Outgoing";
|
? "Incoming" : "Outgoing";
|
||||||
|
|
||||||
var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
|
let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
|
||||||
quality_inspection_field.get_route_options_for_new_doc = function(row) {
|
quality_inspection_field.get_route_options_for_new_doc = function(row) {
|
||||||
if(me.frm.is_new()) return;
|
if(me.frm.is_new()) return;
|
||||||
return {
|
return {
|
||||||
@ -280,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
|
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
let d = locals[cdt][cdn];
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
@ -1949,6 +1957,130 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_quality_inspection: function () {
|
||||||
|
let data = [];
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
label: "Items",
|
||||||
|
fieldtype: "Table",
|
||||||
|
fieldname: "items",
|
||||||
|
cannot_add_rows: true,
|
||||||
|
in_place_edit: true,
|
||||||
|
data: data,
|
||||||
|
get_data: () => {
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
fieldname: "docname",
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Read Only",
|
||||||
|
fieldname: "item_code",
|
||||||
|
label: __("Item Code"),
|
||||||
|
in_list_view: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Read Only",
|
||||||
|
fieldname: "item_name",
|
||||||
|
label: __("Item Name"),
|
||||||
|
in_list_view: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Float",
|
||||||
|
fieldname: "qty",
|
||||||
|
label: __("Accepted Quantity"),
|
||||||
|
in_list_view: true,
|
||||||
|
read_only: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Float",
|
||||||
|
fieldname: "sample_size",
|
||||||
|
label: __("Sample Size"),
|
||||||
|
reqd: true,
|
||||||
|
in_list_view: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
fieldname: "description",
|
||||||
|
label: __("Description"),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
fieldname: "serial_no",
|
||||||
|
label: __("Serial No"),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
fieldname: "batch_no",
|
||||||
|
label: __("Batch No"),
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const me = this;
|
||||||
|
const dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Select Items for Quality Inspection"),
|
||||||
|
fields: fields,
|
||||||
|
primary_action: function () {
|
||||||
|
const data = dialog.get_values();
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.controllers.stock_controller.make_quality_inspections",
|
||||||
|
args: {
|
||||||
|
doctype: me.frm.doc.doctype,
|
||||||
|
docname: me.frm.doc.name,
|
||||||
|
items: data.items
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: function (r) {
|
||||||
|
if (r.message.length > 0) {
|
||||||
|
if (r.message.length === 1) {
|
||||||
|
frappe.set_route("Form", "Quality Inspection", r.message[0]);
|
||||||
|
} else {
|
||||||
|
frappe.route_options = {
|
||||||
|
"reference_type": me.frm.doc.doctype,
|
||||||
|
"reference_name": me.frm.doc.name
|
||||||
|
};
|
||||||
|
frappe.set_route("List", "Quality Inspection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
primary_action_label: __("Create")
|
||||||
|
});
|
||||||
|
|
||||||
|
this.frm.doc.items.forEach(item => {
|
||||||
|
if (!item.quality_inspection) {
|
||||||
|
let dialog_items = dialog.fields_dict.items;
|
||||||
|
dialog_items.df.data.push({
|
||||||
|
"docname": item.name,
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"item_name": item.item_name,
|
||||||
|
"qty": item.qty,
|
||||||
|
"description": item.description,
|
||||||
|
"serial_no": item.serial_no,
|
||||||
|
"batch_no": item.batch_no
|
||||||
|
});
|
||||||
|
dialog_items.grid.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data = dialog.fields_dict.items.df.data;
|
||||||
|
if (!data.length) {
|
||||||
|
frappe.msgprint(__("All items in this document already have a linked Quality Inspection."));
|
||||||
|
} else {
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
get_method_for_payment: function(){
|
get_method_for_payment: function(){
|
||||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||||
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
|
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import erpnext
|
import erpnext
|
||||||
@ -12,7 +10,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError,
|
|||||||
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
|
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
|
||||||
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
|
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
|
from frappe.utils import (cint, cstr, flt, formatdate, getdate,
|
||||||
now_datetime, random_string, strip, get_link_to_form, nowtime)
|
now_datetime, random_string, strip, get_link_to_form, nowtime)
|
||||||
from frappe.utils.html_utils import clean_html
|
from frappe.utils.html_utils import clean_html
|
||||||
from frappe.website.doctype.website_slideshow.website_slideshow import \
|
from frappe.website.doctype.website_slideshow.website_slideshow import \
|
||||||
@ -21,8 +19,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import \
|
|||||||
from frappe.website.render import clear_cache
|
from frappe.website.render import clear_cache
|
||||||
from frappe.website.website_generator import WebsiteGenerator
|
from frappe.website.website_generator import WebsiteGenerator
|
||||||
|
|
||||||
from six import iteritems
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateReorderRows(frappe.ValidationError):
|
class DuplicateReorderRows(frappe.ValidationError):
|
||||||
pass
|
pass
|
||||||
@ -76,8 +72,6 @@ class Item(WebsiteGenerator):
|
|||||||
if not self.description:
|
if not self.description:
|
||||||
self.description = self.item_name
|
self.description = self.item_name
|
||||||
|
|
||||||
# if self.is_sales_item and not self.get('is_item_from_hub'):
|
|
||||||
# self.publish_in_hub = 1
|
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
'''set opening stock and item price'''
|
'''set opening stock and item price'''
|
||||||
@ -129,7 +123,7 @@ class Item(WebsiteGenerator):
|
|||||||
self.cant_change()
|
self.cant_change()
|
||||||
self.update_show_in_website()
|
self.update_show_in_website()
|
||||||
|
|
||||||
if not self.get("__islocal"):
|
if not self.is_new():
|
||||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||||
self.old_website_item_groups = frappe.db.sql_list("""select item_group
|
self.old_website_item_groups = frappe.db.sql_list("""select item_group
|
||||||
from `tabWebsite Item Group`
|
from `tabWebsite Item Group`
|
||||||
@ -203,7 +197,7 @@ class Item(WebsiteGenerator):
|
|||||||
def make_route(self):
|
def make_route(self):
|
||||||
if not self.route:
|
if not self.route:
|
||||||
return cstr(frappe.db.get_value('Item Group', self.item_group,
|
return cstr(frappe.db.get_value('Item Group', self.item_group,
|
||||||
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
|
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
|
||||||
|
|
||||||
def validate_website_image(self):
|
def validate_website_image(self):
|
||||||
if frappe.flags.in_import:
|
if frappe.flags.in_import:
|
||||||
@ -258,7 +252,6 @@ class Item(WebsiteGenerator):
|
|||||||
"attached_to_name": self.name
|
"attached_to_name": self.name
|
||||||
})
|
})
|
||||||
except frappe.DoesNotExistError:
|
except frappe.DoesNotExistError:
|
||||||
pass
|
|
||||||
# cleanup
|
# cleanup
|
||||||
frappe.local.message_log.pop()
|
frappe.local.message_log.pop()
|
||||||
|
|
||||||
@ -362,47 +355,49 @@ class Item(WebsiteGenerator):
|
|||||||
context.update(get_slideshow(self))
|
context.update(get_slideshow(self))
|
||||||
|
|
||||||
def set_attribute_context(self, context):
|
def set_attribute_context(self, context):
|
||||||
if self.has_variants:
|
if not self.has_variants:
|
||||||
attribute_values_available = {}
|
return
|
||||||
context.attribute_values = {}
|
|
||||||
context.selected_attributes = {}
|
|
||||||
|
|
||||||
# load attributes
|
attribute_values_available = {}
|
||||||
for v in context.variants:
|
context.attribute_values = {}
|
||||||
v.attributes = frappe.get_all("Item Variant Attribute",
|
context.selected_attributes = {}
|
||||||
fields=["attribute", "attribute_value"],
|
|
||||||
filters={"parent": v.name})
|
|
||||||
# make a map for easier access in templates
|
|
||||||
v.attribute_map = frappe._dict({})
|
|
||||||
for attr in v.attributes:
|
|
||||||
v.attribute_map[attr.attribute] = attr.attribute_value
|
|
||||||
|
|
||||||
for attr in v.attributes:
|
# load attributes
|
||||||
values = attribute_values_available.setdefault(attr.attribute, [])
|
for v in context.variants:
|
||||||
if attr.attribute_value not in values:
|
v.attributes = frappe.get_all("Item Variant Attribute",
|
||||||
values.append(attr.attribute_value)
|
fields=["attribute", "attribute_value"],
|
||||||
|
filters={"parent": v.name})
|
||||||
|
# make a map for easier access in templates
|
||||||
|
v.attribute_map = frappe._dict({})
|
||||||
|
for attr in v.attributes:
|
||||||
|
v.attribute_map[attr.attribute] = attr.attribute_value
|
||||||
|
|
||||||
if v.name == context.variant.name:
|
for attr in v.attributes:
|
||||||
context.selected_attributes[attr.attribute] = attr.attribute_value
|
values = attribute_values_available.setdefault(attr.attribute, [])
|
||||||
|
if attr.attribute_value not in values:
|
||||||
|
values.append(attr.attribute_value)
|
||||||
|
|
||||||
# filter attributes, order based on attribute table
|
if v.name == context.variant.name:
|
||||||
for attr in self.attributes:
|
context.selected_attributes[attr.attribute] = attr.attribute_value
|
||||||
values = context.attribute_values.setdefault(attr.attribute, [])
|
|
||||||
|
|
||||||
if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
|
# filter attributes, order based on attribute table
|
||||||
for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
|
for attr in self.attributes:
|
||||||
values.append(val)
|
values = context.attribute_values.setdefault(attr.attribute, [])
|
||||||
|
|
||||||
else:
|
if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
|
||||||
# get list of values defined (for sequence)
|
for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
|
||||||
for attr_value in frappe.db.get_all("Item Attribute Value",
|
values.append(val)
|
||||||
fields=["attribute_value"],
|
|
||||||
filters={"parent": attr.attribute}, order_by="idx asc"):
|
|
||||||
|
|
||||||
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
|
else:
|
||||||
values.append(attr_value.attribute_value)
|
# get list of values defined (for sequence)
|
||||||
|
for attr_value in frappe.db.get_all("Item Attribute Value",
|
||||||
|
fields=["attribute_value"],
|
||||||
|
filters={"parent": attr.attribute}, order_by="idx asc"):
|
||||||
|
|
||||||
context.variant_info = json.dumps(context.variants)
|
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
|
||||||
|
values.append(attr_value.attribute_value)
|
||||||
|
|
||||||
|
context.variant_info = json.dumps(context.variants)
|
||||||
|
|
||||||
def set_disabled_attributes(self, context):
|
def set_disabled_attributes(self, context):
|
||||||
"""Disable selection options of attribute combinations that do not result in a variant"""
|
"""Disable selection options of attribute combinations that do not result in a variant"""
|
||||||
@ -521,7 +516,7 @@ class Item(WebsiteGenerator):
|
|||||||
|
|
||||||
def validate_item_type(self):
|
def validate_item_type(self):
|
||||||
if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset:
|
if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset:
|
||||||
msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1)
|
frappe.throw(_("'Has Serial No' can not be 'Yes' for non-stock item"))
|
||||||
|
|
||||||
if self.has_serial_no == 0 and self.serial_no_series:
|
if self.has_serial_no == 0 and self.serial_no_series:
|
||||||
self.serial_no_series = None
|
self.serial_no_series = None
|
||||||
@ -542,10 +537,7 @@ class Item(WebsiteGenerator):
|
|||||||
|
|
||||||
def fill_customer_code(self):
|
def fill_customer_code(self):
|
||||||
""" Append all the customer codes and insert into "customer_code" field of item table """
|
""" Append all the customer codes and insert into "customer_code" field of item table """
|
||||||
cust_code = []
|
self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", []))
|
||||||
for d in self.get('customer_items'):
|
|
||||||
cust_code.append(d.ref_code)
|
|
||||||
self.customer_code = ','.join(cust_code)
|
|
||||||
|
|
||||||
def check_item_tax(self):
|
def check_item_tax(self):
|
||||||
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
||||||
@ -742,23 +734,25 @@ class Item(WebsiteGenerator):
|
|||||||
|
|
||||||
def update_template_item(self):
|
def update_template_item(self):
|
||||||
"""Set Show in Website for Template Item if True for its Variant"""
|
"""Set Show in Website for Template Item if True for its Variant"""
|
||||||
if self.variant_of:
|
if not self.variant_of:
|
||||||
if self.show_in_website:
|
return
|
||||||
self.show_variant_in_website = 1
|
|
||||||
self.show_in_website = 0
|
|
||||||
|
|
||||||
if self.show_variant_in_website:
|
if self.show_in_website:
|
||||||
# show template
|
self.show_variant_in_website = 1
|
||||||
template_item = frappe.get_doc("Item", self.variant_of)
|
self.show_in_website = 0
|
||||||
|
|
||||||
if not template_item.show_in_website:
|
if self.show_variant_in_website:
|
||||||
template_item.show_in_website = 1
|
# show template
|
||||||
template_item.flags.dont_update_variants = True
|
template_item = frappe.get_doc("Item", self.variant_of)
|
||||||
template_item.flags.ignore_permissions = True
|
|
||||||
template_item.save()
|
if not template_item.show_in_website:
|
||||||
|
template_item.show_in_website = 1
|
||||||
|
template_item.flags.dont_update_variants = True
|
||||||
|
template_item.flags.ignore_permissions = True
|
||||||
|
template_item.save()
|
||||||
|
|
||||||
def validate_item_defaults(self):
|
def validate_item_defaults(self):
|
||||||
companies = list(set([row.company for row in self.item_defaults]))
|
companies = {row.company for row in self.item_defaults}
|
||||||
|
|
||||||
if len(companies) != len(self.item_defaults):
|
if len(companies) != len(self.item_defaults):
|
||||||
frappe.throw(_("Cannot set multiple Item Defaults for a company."))
|
frappe.throw(_("Cannot set multiple Item Defaults for a company."))
|
||||||
@ -813,7 +807,7 @@ class Item(WebsiteGenerator):
|
|||||||
frappe.throw(_("Item has variants."))
|
frappe.throw(_("Item has variants."))
|
||||||
|
|
||||||
def validate_attributes_in_variants(self):
|
def validate_attributes_in_variants(self):
|
||||||
if not self.has_variants or self.get("__islocal"):
|
if not self.has_variants or self.is_new():
|
||||||
return
|
return
|
||||||
|
|
||||||
old_doc = self.get_doc_before_save()
|
old_doc = self.get_doc_before_save()
|
||||||
@ -901,7 +895,7 @@ class Item(WebsiteGenerator):
|
|||||||
frappe.throw(_("Variant Based On cannot be changed"))
|
frappe.throw(_("Variant Based On cannot be changed"))
|
||||||
|
|
||||||
def validate_uom(self):
|
def validate_uom(self):
|
||||||
if not self.get("__islocal"):
|
if not self.is_new():
|
||||||
check_stock_uom_with_bin(self.name, self.stock_uom)
|
check_stock_uom_with_bin(self.name, self.stock_uom)
|
||||||
if self.has_variants:
|
if self.has_variants:
|
||||||
for d in frappe.db.get_all("Item", filters={"variant_of": self.name}):
|
for d in frappe.db.get_all("Item", filters={"variant_of": self.name}):
|
||||||
@ -959,20 +953,20 @@ class Item(WebsiteGenerator):
|
|||||||
d.variant_of = self.variant_of
|
d.variant_of = self.variant_of
|
||||||
|
|
||||||
def cant_change(self):
|
def cant_change(self):
|
||||||
if not self.get("__islocal"):
|
if self.is_new():
|
||||||
fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
|
return
|
||||||
|
|
||||||
values = frappe.db.get_value("Item", self.name, fields, as_dict=True)
|
fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
|
||||||
if not values.get('valuation_method') and self.get('valuation_method'):
|
|
||||||
values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
|
|
||||||
|
|
||||||
if values:
|
values = frappe.db.get_value("Item", self.name, fields, as_dict=True)
|
||||||
for field in fields:
|
if not values.get('valuation_method') and self.get('valuation_method'):
|
||||||
if cstr(self.get(field)) != cstr(values.get(field)):
|
values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
|
||||||
if not self.check_if_linked_document_exists(field):
|
|
||||||
break # no linked document, allowed
|
if values:
|
||||||
else:
|
for field in fields:
|
||||||
frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field))))
|
if cstr(self.get(field)) != cstr(values.get(field)):
|
||||||
|
if self.check_if_linked_document_exists(field):
|
||||||
|
frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field))))
|
||||||
|
|
||||||
def check_if_linked_document_exists(self, field):
|
def check_if_linked_document_exists(self, field):
|
||||||
linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item",
|
linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item",
|
||||||
@ -1054,56 +1048,42 @@ def make_item_price(item, price_list_name, item_price):
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
def get_timeline_data(doctype, name):
|
def get_timeline_data(doctype, name):
|
||||||
'''returns timeline data based on stock ledger entry'''
|
"""get timeline data based on Stock Ledger Entry. This is displayed as heatmap on the item page."""
|
||||||
out = {}
|
|
||||||
items = dict(frappe.db.sql('''select posting_date, count(*)
|
|
||||||
from `tabStock Ledger Entry` where item_code=%s
|
|
||||||
and posting_date > date_sub(curdate(), interval 1 year)
|
|
||||||
group by posting_date''', name))
|
|
||||||
|
|
||||||
for date, count in iteritems(items):
|
items = frappe.db.sql("""select unix_timestamp(posting_date), count(*)
|
||||||
timestamp = get_timestamp(date)
|
from `tabStock Ledger Entry`
|
||||||
out.update({timestamp: count})
|
where item_code=%s and posting_date > date_sub(curdate(), interval 1 year)
|
||||||
|
group by posting_date""", name)
|
||||||
|
|
||||||
return out
|
return dict(items)
|
||||||
|
|
||||||
|
|
||||||
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
|
|
||||||
|
def validate_end_of_life(item_code, end_of_life=None, disabled=None):
|
||||||
if (not end_of_life) or (disabled is None):
|
if (not end_of_life) or (disabled is None):
|
||||||
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
|
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
|
||||||
|
|
||||||
if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
|
if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
|
||||||
msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
|
frappe.throw(_("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life)))
|
||||||
_msgprint(msg, verbose)
|
|
||||||
|
|
||||||
if disabled:
|
if disabled:
|
||||||
_msgprint(_("Item {0} is disabled").format(item_code), verbose)
|
frappe.throw(_("Item {0} is disabled").format(item_code))
|
||||||
|
|
||||||
|
|
||||||
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
|
def validate_is_stock_item(item_code, is_stock_item=None):
|
||||||
if not is_stock_item:
|
if not is_stock_item:
|
||||||
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
|
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
|
||||||
|
|
||||||
if is_stock_item != 1:
|
if is_stock_item != 1:
|
||||||
msg = _("Item {0} is not a stock Item").format(item_code)
|
frappe.throw(_("Item {0} is not a stock Item").format(item_code))
|
||||||
|
|
||||||
_msgprint(msg, verbose)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_cancelled_item(item_code, docstatus=None, verbose=1):
|
def validate_cancelled_item(item_code, docstatus=None):
|
||||||
if docstatus is None:
|
if docstatus is None:
|
||||||
docstatus = frappe.db.get_value("Item", item_code, "docstatus")
|
docstatus = frappe.db.get_value("Item", item_code, "docstatus")
|
||||||
|
|
||||||
if docstatus == 2:
|
if docstatus == 2:
|
||||||
msg = _("Item {0} is cancelled").format(item_code)
|
frappe.throw(_("Item {0} is cancelled").format(item_code))
|
||||||
_msgprint(msg, verbose)
|
|
||||||
|
|
||||||
def _msgprint(msg, verbose):
|
|
||||||
if verbose:
|
|
||||||
msgprint(msg, raise_exception=True)
|
|
||||||
else:
|
|
||||||
raise frappe.ValidationError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||||
"""returns last purchase details in stock uom"""
|
"""returns last purchase details in stock uom"""
|
||||||
@ -1203,27 +1183,25 @@ def check_stock_uom_with_bin(item, stock_uom):
|
|||||||
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
|
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
|
||||||
return
|
return
|
||||||
|
|
||||||
matched = True
|
|
||||||
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
||||||
{"item_code": item}, "stock_uom")
|
{"item_code": item}, "stock_uom")
|
||||||
|
|
||||||
if ref_uom:
|
if ref_uom:
|
||||||
if cstr(ref_uom) != cstr(stock_uom):
|
if cstr(ref_uom) != cstr(stock_uom):
|
||||||
matched = False
|
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
|
||||||
else:
|
|
||||||
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
|
|
||||||
for bin in bin_list:
|
|
||||||
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
|
|
||||||
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
|
|
||||||
matched = False
|
|
||||||
break
|
|
||||||
|
|
||||||
if matched and bin_list:
|
bin_list = frappe.db.sql("""
|
||||||
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
|
select * from tabBin where item_code = %s
|
||||||
|
and (reserved_qty > 0 or ordered_qty > 0 or indented_qty > 0 or planned_qty > 0)
|
||||||
|
and stock_uom != %s
|
||||||
|
""", (item, stock_uom), as_dict=1)
|
||||||
|
|
||||||
|
if bin_list:
|
||||||
|
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item.").format(item))
|
||||||
|
|
||||||
|
# No SLE or documents against item. Bin UOM can be changed safely.
|
||||||
|
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
|
||||||
|
|
||||||
if not matched:
|
|
||||||
frappe.throw(
|
|
||||||
_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
|
|
||||||
|
|
||||||
def get_item_defaults(item_code, company):
|
def get_item_defaults(item_code, company):
|
||||||
item = frappe.get_cached_doc('Item', item_code)
|
item = frappe.get_cached_doc('Item', item_code)
|
||||||
@ -1264,45 +1242,59 @@ def get_item_details(item_code, company=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_uom_conv_factor(uom, stock_uom):
|
def get_uom_conv_factor(uom, stock_uom):
|
||||||
uoms = [uom, stock_uom]
|
""" Get UOM conversion factor from uom to stock_uom
|
||||||
value = ""
|
e.g. uom = "Kg", stock_uom = "Gram" then returns 1000.0
|
||||||
uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\
|
"""
|
||||||
where to_uom in ({0})
|
if uom == stock_uom:
|
||||||
""".format(', '.join([frappe.db.escape(i, percent=False) for i in uoms])), as_dict=True)
|
return 1.0
|
||||||
|
|
||||||
for d in uom_details:
|
from_uom, to_uom = uom, stock_uom # renaming for readability
|
||||||
if d.from_uom == stock_uom and d.to_uom == uom:
|
|
||||||
value = 1/flt(d.value)
|
|
||||||
elif d.from_uom == uom and d.to_uom == stock_uom:
|
|
||||||
value = d.value
|
|
||||||
|
|
||||||
if not value:
|
exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1)
|
||||||
uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1)
|
if exact_match:
|
||||||
uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1)
|
return exact_match.value
|
||||||
|
|
||||||
if uom_stock and uom_row:
|
inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1)
|
||||||
if uom_stock.from_uom == uom_row.from_uom:
|
if inverse_match:
|
||||||
value = flt(uom_stock.value) * 1/flt(uom_row.value)
|
return 1 / inverse_match.value
|
||||||
|
|
||||||
|
# This attempts to try and get conversion from intermediate UOM.
|
||||||
|
# case:
|
||||||
|
# g -> mg = 1000
|
||||||
|
# g -> kg = 0.001
|
||||||
|
# therefore kg -> mg = 1000 / 0.001 = 1,000,000
|
||||||
|
intermediate_match = frappe.db.sql("""
|
||||||
|
select (first.value / second.value) as value
|
||||||
|
from `tabUOM Conversion Factor` first
|
||||||
|
join `tabUOM Conversion Factor` second
|
||||||
|
on first.from_uom = second.from_uom
|
||||||
|
where
|
||||||
|
first.to_uom = %(to_uom)s
|
||||||
|
and second.to_uom = %(from_uom)s
|
||||||
|
limit 1
|
||||||
|
""", {"to_uom": to_uom, "from_uom": from_uom}, as_dict=1)
|
||||||
|
|
||||||
|
if intermediate_match:
|
||||||
|
return intermediate_match[0].value
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_attribute(parent, attribute_value=''):
|
def get_item_attribute(parent, attribute_value=""):
|
||||||
|
"""Used for providing auto-completions in child table."""
|
||||||
if not frappe.has_permission("Item"):
|
if not frappe.has_permission("Item"):
|
||||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
frappe.throw(_("No Permission"))
|
||||||
|
|
||||||
return frappe.get_all("Item Attribute Value", fields = ["attribute_value"],
|
return frappe.get_all("Item Attribute Value", fields = ["attribute_value"],
|
||||||
filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)})
|
filters = {'parent': parent, 'attribute_value': ("like", f"%{attribute_value}%")})
|
||||||
|
|
||||||
def update_variants(variants, template, publish_progress=True):
|
def update_variants(variants, template, publish_progress=True):
|
||||||
count=0
|
total = len(variants)
|
||||||
for d in variants:
|
for count, d in enumerate(variants, start=1):
|
||||||
variant = frappe.get_doc("Item", d)
|
variant = frappe.get_doc("Item", d)
|
||||||
copy_attributes_to_variant(template, variant)
|
copy_attributes_to_variant(template, variant)
|
||||||
variant.save()
|
variant.save()
|
||||||
count+=1
|
|
||||||
if publish_progress:
|
if publish_progress:
|
||||||
frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
|
frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
|
||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
# since route is a Text column, it needs a length for indexing
|
# since route is a Text column, it needs a length for indexing
|
||||||
|
|||||||
@ -10,14 +10,15 @@ from frappe.test_runner import make_test_objects
|
|||||||
from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError,
|
from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError,
|
||||||
InvalidItemAttributeValueError, get_variant)
|
InvalidItemAttributeValueError, get_variant)
|
||||||
from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode
|
from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode
|
||||||
from erpnext.stock.doctype.item.item import get_uom_conv_factor
|
from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attribute,
|
||||||
|
validate_is_stock_item, get_timeline_data)
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
from erpnext.tests.utils import change_settings
|
||||||
|
|
||||||
from six import iteritems
|
|
||||||
|
|
||||||
test_ignore = ["BOM"]
|
test_ignore = ["BOM"]
|
||||||
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"]
|
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
|
||||||
|
|
||||||
def make_item(item_code, properties=None):
|
def make_item(item_code, properties=None):
|
||||||
if frappe.db.exists("Item", item_code):
|
if frappe.db.exists("Item", item_code):
|
||||||
@ -98,7 +99,7 @@ class TestItem(unittest.TestCase):
|
|||||||
"ignore_pricing_rule": 1
|
"ignore_pricing_rule": 1
|
||||||
})
|
})
|
||||||
|
|
||||||
for key, value in iteritems(to_check):
|
for key, value in to_check.items():
|
||||||
self.assertEqual(value, details.get(key))
|
self.assertEqual(value, details.get(key))
|
||||||
|
|
||||||
def test_item_tax_template(self):
|
def test_item_tax_template(self):
|
||||||
@ -194,7 +195,7 @@ class TestItem(unittest.TestCase):
|
|||||||
"plc_conversion_rate": 1,
|
"plc_conversion_rate": 1,
|
||||||
"customer": "_Test Customer",
|
"customer": "_Test Customer",
|
||||||
})
|
})
|
||||||
for key, value in iteritems(sales_item_check):
|
for key, value in sales_item_check.items():
|
||||||
self.assertEqual(value, sales_item_details.get(key))
|
self.assertEqual(value, sales_item_details.get(key))
|
||||||
|
|
||||||
purchase_item_check = {
|
purchase_item_check = {
|
||||||
@ -215,7 +216,7 @@ class TestItem(unittest.TestCase):
|
|||||||
"plc_conversion_rate": 1,
|
"plc_conversion_rate": 1,
|
||||||
"supplier": "_Test Supplier",
|
"supplier": "_Test Supplier",
|
||||||
})
|
})
|
||||||
for key, value in iteritems(purchase_item_check):
|
for key, value in purchase_item_check.items():
|
||||||
self.assertEqual(value, purchase_item_details.get(key))
|
self.assertEqual(value, purchase_item_details.get(key))
|
||||||
|
|
||||||
def test_item_attribute_change_after_variant(self):
|
def test_item_attribute_change_after_variant(self):
|
||||||
@ -375,6 +376,14 @@ class TestItem(unittest.TestCase):
|
|||||||
self.assertEqual(item_doc.uoms[1].uom, "Kg")
|
self.assertEqual(item_doc.uoms[1].uom, "Kg")
|
||||||
self.assertEqual(item_doc.uoms[1].conversion_factor, 1000)
|
self.assertEqual(item_doc.uoms[1].conversion_factor, 1000)
|
||||||
|
|
||||||
|
def test_uom_conv_intermediate(self):
|
||||||
|
factor = get_uom_conv_factor("Pound", "Gram")
|
||||||
|
self.assertAlmostEqual(factor, 453.592, 3)
|
||||||
|
|
||||||
|
def test_uom_conv_base_case(self):
|
||||||
|
factor = get_uom_conv_factor("m", "m")
|
||||||
|
self.assertEqual(factor, 1.0)
|
||||||
|
|
||||||
def test_item_variant_by_manufacturer(self):
|
def test_item_variant_by_manufacturer(self):
|
||||||
fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}]
|
fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}]
|
||||||
set_item_variant_settings(fields)
|
set_item_variant_settings(fields)
|
||||||
@ -464,7 +473,7 @@ class TestItem(unittest.TestCase):
|
|||||||
self.assertEqual(len(matching_barcodes), 1)
|
self.assertEqual(len(matching_barcodes), 1)
|
||||||
details = matching_barcodes[0]
|
details = matching_barcodes[0]
|
||||||
|
|
||||||
for key, value in iteritems(barcode_properties):
|
for key, value in barcode_properties.items():
|
||||||
self.assertEqual(value, details.get(key))
|
self.assertEqual(value, details.get(key))
|
||||||
|
|
||||||
# Add barcode again - should cause DuplicateEntryError
|
# Add barcode again - should cause DuplicateEntryError
|
||||||
@ -480,6 +489,89 @@ class TestItem(unittest.TestCase):
|
|||||||
new_barcode.barcode_type = 'EAN'
|
new_barcode.barcode_type = 'EAN'
|
||||||
self.assertRaises(InvalidBarcode, item_doc.save)
|
self.assertRaises(InvalidBarcode, item_doc.save)
|
||||||
|
|
||||||
|
def test_heatmap_data(self):
|
||||||
|
import time
|
||||||
|
data = get_timeline_data("Item", "_Test Item")
|
||||||
|
self.assertTrue(isinstance(data, dict))
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
one_year_ago = now - 366 * 24 * 60 * 60
|
||||||
|
|
||||||
|
for timestamp, count in data.items():
|
||||||
|
self.assertIsInstance(timestamp, int)
|
||||||
|
self.assertTrue(one_year_ago <= timestamp <= now)
|
||||||
|
self.assertIsInstance(count, int)
|
||||||
|
self.assertTrue(count >= 0)
|
||||||
|
|
||||||
|
def test_index_creation(self):
|
||||||
|
"check if index is getting created in db"
|
||||||
|
from erpnext.stock.doctype.item.item import on_doctype_update
|
||||||
|
on_doctype_update()
|
||||||
|
|
||||||
|
indices = frappe.db.sql("show index from tabItem", as_dict=1)
|
||||||
|
expected_columns = {"item_code", "item_name", "item_group", "route"}
|
||||||
|
for index in indices:
|
||||||
|
expected_columns.discard(index.get("Column_name"))
|
||||||
|
|
||||||
|
if expected_columns:
|
||||||
|
self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}")
|
||||||
|
|
||||||
|
def test_attribute_completions(self):
|
||||||
|
expected_attrs = {"Small", "Extra Small", "Extra Large", "Large", "2XL", "Medium"}
|
||||||
|
|
||||||
|
attrs = get_item_attribute("Test Size")
|
||||||
|
received_attrs = {attr.attribute_value for attr in attrs}
|
||||||
|
self.assertEqual(received_attrs, expected_attrs)
|
||||||
|
|
||||||
|
attrs = get_item_attribute("Test Size", attribute_value="extra")
|
||||||
|
received_attrs = {attr.attribute_value for attr in attrs}
|
||||||
|
self.assertEqual(received_attrs, {"Extra Small", "Extra Large"})
|
||||||
|
|
||||||
|
def test_check_stock_uom_with_bin(self):
|
||||||
|
# this item has opening stock and stock_uom set in test_records.
|
||||||
|
item = frappe.get_doc("Item", "_Test Item")
|
||||||
|
item.stock_uom = "Gram"
|
||||||
|
self.assertRaises(frappe.ValidationError, item.save)
|
||||||
|
|
||||||
|
def test_check_stock_uom_with_bin_no_sle(self):
|
||||||
|
from erpnext.stock.stock_balance import update_bin_qty
|
||||||
|
item = create_item("_Item with bin qty")
|
||||||
|
item.stock_uom = "Gram"
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
update_bin_qty(item.item_code, "_Test Warehouse - _TC", {
|
||||||
|
"reserved_qty": 10
|
||||||
|
})
|
||||||
|
|
||||||
|
item.stock_uom = "Kilometer"
|
||||||
|
self.assertRaises(frappe.ValidationError, item.save)
|
||||||
|
|
||||||
|
update_bin_qty(item.item_code, "_Test Warehouse - _TC", {
|
||||||
|
"reserved_qty": 0
|
||||||
|
})
|
||||||
|
|
||||||
|
item.load_from_db()
|
||||||
|
item.stock_uom = "Kilometer"
|
||||||
|
try:
|
||||||
|
item.save()
|
||||||
|
except frappe.ValidationError as e:
|
||||||
|
self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}")
|
||||||
|
|
||||||
|
def test_validate_stock_item(self):
|
||||||
|
self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item")
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_is_stock_item("_Test Item")
|
||||||
|
except frappe.ValidationError as e:
|
||||||
|
self.fail(f"stock item considered non-stock item: {e}")
|
||||||
|
|
||||||
|
@change_settings("Stock Settings", {"item_naming_by": "Naming Series"})
|
||||||
|
def test_autoname_series(self):
|
||||||
|
item = frappe.new_doc("Item")
|
||||||
|
item.item_group = "All Item Groups"
|
||||||
|
item.save() # if item code saved without item_code then series worked
|
||||||
|
|
||||||
|
|
||||||
def set_item_variant_settings(fields):
|
def set_item_variant_settings(fields):
|
||||||
doc = frappe.get_doc('Item Variant Settings')
|
doc = frappe.get_doc('Item Variant Settings')
|
||||||
doc.set('fields', fields)
|
doc.set('fields', fields)
|
||||||
@ -494,23 +586,24 @@ def make_item_variant():
|
|||||||
|
|
||||||
test_records = frappe.get_test_records('Item')
|
test_records = frappe.get_test_records('Item')
|
||||||
|
|
||||||
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None,
|
def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC",
|
||||||
customer=None, is_purchase_item=None, opening_stock=None, company=None):
|
is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0,
|
||||||
|
company="_Test Company"):
|
||||||
if not frappe.db.exists("Item", item_code):
|
if not frappe.db.exists("Item", item_code):
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
item.item_code = item_code
|
item.item_code = item_code
|
||||||
item.item_name = item_code
|
item.item_name = item_code
|
||||||
item.description = item_code
|
item.description = item_code
|
||||||
item.item_group = "All Item Groups"
|
item.item_group = "All Item Groups"
|
||||||
item.is_stock_item = is_stock_item or 1
|
item.is_stock_item = is_stock_item
|
||||||
item.opening_stock = opening_stock or 0
|
item.opening_stock = opening_stock
|
||||||
item.valuation_rate = valuation_rate or 0.0
|
item.valuation_rate = valuation_rate
|
||||||
item.is_purchase_item = is_purchase_item
|
item.is_purchase_item = is_purchase_item
|
||||||
item.is_customer_provided_item = is_customer_provided_item
|
item.is_customer_provided_item = is_customer_provided_item
|
||||||
item.customer = customer or ''
|
item.customer = customer or ''
|
||||||
item.append("item_defaults", {
|
item.append("item_defaults", {
|
||||||
"default_warehouse": warehouse or '_Test Warehouse - _TC',
|
"default_warehouse": warehouse,
|
||||||
"company": company or "_Test Company"
|
"company": company
|
||||||
})
|
})
|
||||||
item.save()
|
item.save()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,29 +1,45 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
|
||||||
|
from erpnext.controllers.stock_controller import (
|
||||||
|
QualityInspectionNotSubmittedError,
|
||||||
|
QualityInspectionRejectedError,
|
||||||
|
QualityInspectionRequiredError,
|
||||||
|
make_quality_inspections,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError
|
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Quality Inspection')
|
# test_records = frappe.get_test_records('Quality Inspection')
|
||||||
|
|
||||||
|
|
||||||
class TestQualityInspection(unittest.TestCase):
|
class TestQualityInspection(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_item("_Test Item with QA")
|
create_item("_Test Item with QA")
|
||||||
frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1)
|
frappe.db.set_value(
|
||||||
|
"Item", "_Test Item with QA", "inspection_required_before_delivery", 1
|
||||||
|
)
|
||||||
|
|
||||||
def test_qa_for_delivery(self):
|
def test_qa_for_delivery(self):
|
||||||
make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100)
|
make_stock_entry(
|
||||||
|
item_code="_Test Item with QA",
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=1,
|
||||||
|
basic_rate=100
|
||||||
|
)
|
||||||
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||||
|
|
||||||
self.assertRaises(QualityInspectionRequiredError, dn.submit)
|
self.assertRaises(QualityInspectionRequiredError, dn.submit)
|
||||||
|
|
||||||
qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected")
|
qa = create_quality_inspection(
|
||||||
|
reference_type="Delivery Note", reference_name=dn.name, status="Rejected"
|
||||||
|
)
|
||||||
dn.reload()
|
dn.reload()
|
||||||
self.assertRaises(QualityInspectionRejectedError, dn.submit)
|
self.assertRaises(QualityInspectionRejectedError, dn.submit)
|
||||||
|
|
||||||
@ -38,7 +54,9 @@ class TestQualityInspection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_qa_not_submit(self):
|
def test_qa_not_submit(self):
|
||||||
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||||
qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True)
|
qa = create_quality_inspection(
|
||||||
|
reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True
|
||||||
|
)
|
||||||
dn.items[0].quality_inspection = qa.name
|
dn.items[0].quality_inspection = qa.name
|
||||||
self.assertRaises(QualityInspectionNotSubmittedError, dn.submit)
|
self.assertRaises(QualityInspectionNotSubmittedError, dn.submit)
|
||||||
|
|
||||||
@ -48,21 +66,28 @@ class TestQualityInspection(unittest.TestCase):
|
|||||||
def test_value_based_qi_readings(self):
|
def test_value_based_qi_readings(self):
|
||||||
# Test QI based on acceptance values (Non formula)
|
# Test QI based on acceptance values (Non formula)
|
||||||
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||||
readings = [{
|
readings = [
|
||||||
"specification": "Iron Content", # numeric reading
|
{
|
||||||
"min_value": 0.1,
|
"specification": "Iron Content", # numeric reading
|
||||||
"max_value": 0.9,
|
"min_value": 0.1,
|
||||||
"reading_1": "0.4"
|
"max_value": 0.9,
|
||||||
},
|
"reading_1": "0.4"
|
||||||
{
|
},
|
||||||
"specification": "Particle Inspection Needed", # non-numeric reading
|
{
|
||||||
"numeric": 0,
|
"specification": "Particle Inspection Needed", # non-numeric reading
|
||||||
"value": "Yes",
|
"numeric": 0,
|
||||||
"reading_value": "Yes"
|
"value": "Yes",
|
||||||
}]
|
"reading_value": "Yes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
qa = create_quality_inspection(
|
||||||
|
reference_type="Delivery Note",
|
||||||
|
reference_name=dn.name,
|
||||||
|
readings=readings,
|
||||||
|
do_not_save=True
|
||||||
|
)
|
||||||
|
|
||||||
qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
|
|
||||||
readings=readings, do_not_save=True)
|
|
||||||
qa.save()
|
qa.save()
|
||||||
|
|
||||||
# status must be auto set as per formula
|
# status must be auto set as per formula
|
||||||
@ -74,36 +99,43 @@ class TestQualityInspection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_formula_based_qi_readings(self):
|
def test_formula_based_qi_readings(self):
|
||||||
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||||
readings = [{
|
readings = [
|
||||||
"specification": "Iron Content", # numeric reading
|
{
|
||||||
"formula_based_criteria": 1,
|
"specification": "Iron Content", # numeric reading
|
||||||
"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
|
"formula_based_criteria": 1,
|
||||||
"reading_1": "0.4"
|
"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
|
||||||
},
|
"reading_1": "0.4"
|
||||||
{
|
},
|
||||||
"specification": "Calcium Content", # numeric reading
|
{
|
||||||
"formula_based_criteria": 1,
|
"specification": "Calcium Content", # numeric reading
|
||||||
"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
|
"formula_based_criteria": 1,
|
||||||
"reading_1": "0.7"
|
"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
|
||||||
},
|
"reading_1": "0.7"
|
||||||
{
|
},
|
||||||
"specification": "Mg Content", # numeric reading
|
{
|
||||||
"formula_based_criteria": 1,
|
"specification": "Mg Content", # numeric reading
|
||||||
"acceptance_formula": "mean < 0.9",
|
"formula_based_criteria": 1,
|
||||||
"reading_1": "0.5",
|
"acceptance_formula": "mean < 0.9",
|
||||||
"reading_2": "0.7",
|
"reading_1": "0.5",
|
||||||
"reading_3": "random text" # check if random string input causes issues
|
"reading_2": "0.7",
|
||||||
},
|
"reading_3": "random text" # check if random string input causes issues
|
||||||
{
|
},
|
||||||
"specification": "Calcium Content", # non-numeric reading
|
{
|
||||||
"formula_based_criteria": 1,
|
"specification": "Calcium Content", # non-numeric reading
|
||||||
"numeric": 0,
|
"formula_based_criteria": 1,
|
||||||
"acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
|
"numeric": 0,
|
||||||
"reading_value": "Grade B"
|
"acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
|
||||||
}]
|
"reading_value": "Grade B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
qa = create_quality_inspection(
|
||||||
|
reference_type="Delivery Note",
|
||||||
|
reference_name=dn.name,
|
||||||
|
readings=readings,
|
||||||
|
do_not_save=True
|
||||||
|
)
|
||||||
|
|
||||||
qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
|
|
||||||
readings=readings, do_not_save=True)
|
|
||||||
qa.save()
|
qa.save()
|
||||||
|
|
||||||
# status must be auto set as per formula
|
# status must be auto set as per formula
|
||||||
@ -115,6 +147,19 @@ class TestQualityInspection(unittest.TestCase):
|
|||||||
qa.delete()
|
qa.delete()
|
||||||
dn.delete()
|
dn.delete()
|
||||||
|
|
||||||
|
def test_make_quality_inspections_from_linked_document(self):
|
||||||
|
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||||
|
for item in dn.items:
|
||||||
|
item.sample_size = item.qty
|
||||||
|
quality_inspections = make_quality_inspections(dn.doctype, dn.name, dn.items)
|
||||||
|
self.assertEqual(len(dn.items), len(quality_inspections))
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
for qi in quality_inspections:
|
||||||
|
frappe.delete_doc("Quality Inspection", qi)
|
||||||
|
dn.delete()
|
||||||
|
|
||||||
|
|
||||||
def create_quality_inspection(**args):
|
def create_quality_inspection(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
qa = frappe.new_doc("Quality Inspection")
|
qa = frappe.new_doc("Quality Inspection")
|
||||||
@ -134,7 +179,7 @@ def create_quality_inspection(**args):
|
|||||||
readings = args.readings
|
readings = args.readings
|
||||||
|
|
||||||
if args.status == "Rejected":
|
if args.status == "Rejected":
|
||||||
readings["reading_1"] = "12" # status is auto set in child on save
|
readings["reading_1"] = "12" # status is auto set in child on save
|
||||||
|
|
||||||
if isinstance(readings, list):
|
if isinstance(readings, list):
|
||||||
for entry in readings:
|
for entry in readings:
|
||||||
@ -150,6 +195,7 @@ def create_quality_inspection(**args):
|
|||||||
|
|
||||||
return qa
|
return qa
|
||||||
|
|
||||||
|
|
||||||
def create_quality_inspection_parameter(parameter):
|
def create_quality_inspection_parameter(parameter):
|
||||||
if not frappe.db.exists("Quality Inspection Parameter", parameter):
|
if not frappe.db.exists("Quality Inspection Parameter", parameter):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
|
|||||||
@ -115,6 +115,14 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!frm.is_new() && frm.doc.docstatus === 0) {
|
||||||
|
frm.add_custom_button(__("Quality Inspection(s)"), () => {
|
||||||
|
let transaction_controller = new erpnext.TransactionController({ frm: frm });
|
||||||
|
transaction_controller.make_quality_inspection();
|
||||||
|
}, __("Create"));
|
||||||
|
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
let quality_inspection_field = frm.get_docfield("items", "quality_inspection");
|
let quality_inspection_field = frm.get_docfield("items", "quality_inspection");
|
||||||
quality_inspection_field.get_route_options_for_new_doc = function(row) {
|
quality_inspection_field.get_route_options_for_new_doc = function(row) {
|
||||||
if (frm.is_new()) return;
|
if (frm.is_new()) return;
|
||||||
@ -155,7 +163,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if(!frm.doc.docstatus) {
|
if(!frm.doc.docstatus) {
|
||||||
frm.trigger('validate_purpose_consumption');
|
frm.trigger('validate_purpose_consumption');
|
||||||
frm.add_custom_button(__('Create Material Request'), function() {
|
frm.add_custom_button(__('Material Request'), function() {
|
||||||
frappe.model.with_doctype('Material Request', function() {
|
frappe.model.with_doctype('Material Request', function() {
|
||||||
var mr = frappe.model.get_new_doc('Material Request');
|
var mr = frappe.model.get_new_doc('Material Request');
|
||||||
var items = frm.get_field('items').grid.get_selected_children();
|
var items = frm.get_field('items').grid.get_selected_children();
|
||||||
@ -178,7 +186,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
});
|
});
|
||||||
frappe.set_route('Form', 'Material Request', mr.name);
|
frappe.set_route('Form', 'Material Request', mr.name);
|
||||||
});
|
});
|
||||||
});
|
}, __("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.items) {
|
if(frm.doc.items) {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
def validate_data(self):
|
def validate_data(self):
|
||||||
def _get_msg(row_num, msg):
|
def _get_msg(row_num, msg):
|
||||||
return _("Row # {0}: ").format(row_num+1) + msg
|
return _("Row # {0}:").format(row_num+1) + " " + msg
|
||||||
|
|
||||||
self.validation_messages = []
|
self.validation_messages = []
|
||||||
item_warehouse_combinations = []
|
item_warehouse_combinations = []
|
||||||
@ -167,8 +167,8 @@ class StockReconciliation(StockController):
|
|||||||
item = frappe.get_doc("Item", item_code)
|
item = frappe.get_doc("Item", item_code)
|
||||||
|
|
||||||
# end of life and stock item
|
# end of life and stock item
|
||||||
validate_end_of_life(item_code, item.end_of_life, item.disabled, verbose=0)
|
validate_end_of_life(item_code, item.end_of_life, item.disabled)
|
||||||
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
|
validate_is_stock_item(item_code, item.is_stock_item)
|
||||||
|
|
||||||
# item should not be serialized
|
# item should not be serialized
|
||||||
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
||||||
@ -179,10 +179,10 @@ class StockReconciliation(StockController):
|
|||||||
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
||||||
|
|
||||||
# docstatus should be < 2
|
# docstatus should be < 2
|
||||||
validate_cancelled_item(item_code, item.docstatus, verbose=0)
|
validate_cancelled_item(item_code, item.docstatus)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e))
|
self.validation_messages.append(_("Row #") + " " + ("%d: " % (row.idx)) + cstr(e))
|
||||||
|
|
||||||
def update_stock_ledger(self):
|
def update_stock_ledger(self):
|
||||||
""" find difference between current and expected entries
|
""" find difference between current and expected entries
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
import copy
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
@ -41,3 +42,38 @@ def create_test_contact_and_address():
|
|||||||
contact.add_email("test_contact_customer@example.com", is_primary=True)
|
contact.add_email("test_contact_customer@example.com", is_primary=True)
|
||||||
contact.add_phone("+91 0000000000", is_primary_phone=True)
|
contact.add_phone("+91 0000000000", is_primary_phone=True)
|
||||||
contact.insert()
|
contact.insert()
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def change_settings(doctype, settings_dict):
|
||||||
|
""" A context manager to ensure that settings are changed before running
|
||||||
|
function and restored after running it regardless of exceptions occured.
|
||||||
|
This is useful in tests where you want to make changes in a function but
|
||||||
|
don't retain those changes.
|
||||||
|
import and use as decorator to cover full function or using `with` statement.
|
||||||
|
|
||||||
|
example:
|
||||||
|
@change_settings("Stock Settings", {"item_naming_by": "Naming Series"})
|
||||||
|
def test_case(self):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
settings = frappe.get_doc(doctype)
|
||||||
|
# remember setting
|
||||||
|
previous_settings = copy.deepcopy(settings_dict)
|
||||||
|
for key in previous_settings:
|
||||||
|
previous_settings[key] = getattr(settings, key)
|
||||||
|
|
||||||
|
# change setting
|
||||||
|
for key, value in settings_dict.items():
|
||||||
|
setattr(settings, key, value)
|
||||||
|
settings.save()
|
||||||
|
yield # yield control to calling function
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# restore settings
|
||||||
|
settings = frappe.get_doc(doctype)
|
||||||
|
for key, value in previous_settings.items():
|
||||||
|
setattr(settings, key, value)
|
||||||
|
settings.save()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user