Merge branch 'develop' into prod-plan-status

This commit is contained in:
Marica 2020-10-29 13:02:19 +05:30 committed by GitHub
commit 1b8a1cfed2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 925 additions and 763 deletions

View File

@ -101,7 +101,7 @@ class Account(NestedSet):
return return
if not frappe.db.get_value("Account", if not frappe.db.get_value("Account",
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'): {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account: elif self.parent_account:
descendants = get_descendants_of('Company', self.company) descendants = get_descendants_of('Company', self.company)
if not descendants: return if not descendants: return
@ -164,9 +164,19 @@ class Account(NestedSet):
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants: for company in descendants:
company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company): if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA") frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company, parent_acc_name)) .format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
# validate if parent of child company account to be added is a group
if (frappe.db.get_value("Account", self.parent_account, "is_group")
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
msg += "<br><br>"
msg += _("Please convert the parent account in corresponding child company to a group account.")
frappe.throw(msg, title=_("Invalid Parent Account"))
filters = { filters = {
"account_name": self.account_name, "account_name": self.account_name,
@ -309,8 +319,9 @@ def update_account_number(name, account_name, account_number=None, from_descenda
allow_child_account_creation = _("Allow Account Creation Against Child Company") allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor)) message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
message += "<br>" + _("Renaming it is only allowed via parent company {0}, \ message += "<br>"
to avoid mismatch.").format(frappe.bold(ancestor)) + "<br><br>" message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company)) message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
frappe.throw(message, title=_("Rename Not Allowed")) frappe.throw(message, title=_("Rename Not Allowed"))

View File

@ -111,6 +111,17 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4") self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5") self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
def test_add_account_to_a_group(self):
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Office Rent - _TC3"
acc.company = "_Test Company 3"
self.assertRaises(frappe.ValidationError, acc.insert)
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
def test_account_rename_sync(self): def test_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) frappe.local.flags.pop("ignore_root_company_validation", None)
@ -160,6 +171,7 @@ class TestAccount(unittest.TestCase):
for doc in to_delete: for doc in to_delete:
frappe.delete_doc("Account", doc) frappe.delete_doc("Account", doc)
def _make_test_records(verbose): def _make_test_records(verbose):
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects

View File

@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', {
frm.set_query('document_type', () => { frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list; let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push('Accounting Dimension', 'Project', invalid_doctypes.push('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail'); 'Cost Center', 'Accounting Dimension Detail', 'Company');
return { return {
filters: { filters: {

View File

@ -19,7 +19,7 @@ class AccountingDimension(Document):
def validate(self): def validate(self):
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project', if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail') : 'Cost Center', 'Accounting Dimension Detail', 'Company') :
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg) frappe.throw(msg)

View File

@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field):
FROM FROM
`tabPOS Invoice` p {from_sales_invoice_payment} `tabPOS Invoice` p {from_sales_invoice_payment}
WHERE WHERE
p.docstatus = 1 and
{group_by_mop_condition} {group_by_mop_condition}
{conditions} {conditions}
ORDER BY ORDER BY

View File

@ -9,9 +9,9 @@
"filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}", "filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
"group_by_type": "Count", "group_by_type": "Count",
"idx": 0, "idx": 0,
"is_public": 0, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2020-07-23 13:53:33.211371", "modified": "2020-10-28 23:15:58.432189",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Value Analytics", "name": "Asset Value Analytics",

View File

@ -8,9 +8,9 @@
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}", "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
"idx": 0, "idx": 0,
"is_public": 0, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2020-07-23 13:39:32.429240", "modified": "2020-10-28 23:16:16.939070",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Category-wise Asset Value", "name": "Category-wise Asset Value",

View File

@ -8,9 +8,9 @@
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}", "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
"idx": 0, "idx": 0,
"is_public": 0, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2020-07-23 13:42:44.912551", "modified": "2020-10-28 23:16:07.883312",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Location-wise Asset Value", "name": "Location-wise Asset Value",

View File

@ -19,6 +19,8 @@ from erpnext.controllers.accounts_controller import update_child_qty_rate
from erpnext.controllers.status_updater import OverAllowanceError from erpnext.controllers.status_updater import OverAllowanceError
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
from erpnext.stock.doctype.batch.test_batch import make_new_batch
from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
class TestPurchaseOrder(unittest.TestCase): class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self): def test_make_purchase_receipt(self):
@ -686,7 +688,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_exploded_items_in_subcontracted(self): def test_exploded_items_in_subcontracted(self):
item_code = "_Test Subcontracted FG Item 1" item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code) make_subcontracted_item(item_code=item_code)
po = create_purchase_order(item_code=item_code, qty=1, po = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1) is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
@ -708,7 +710,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_backflush_based_on_stock_entry(self): def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1" item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code) make_subcontracted_item(item_code=item_code)
make_item('Sub Contracted Raw Material 1', { make_item('Sub Contracted Raw Material 1', {
'is_stock_item': 1, 'is_stock_item': 1,
'is_sub_contracted_item': 1 'is_sub_contracted_item': 1
@ -767,6 +769,129 @@ class TestPurchaseOrder(unittest.TestCase):
update_backflush_based_on("BOM") update_backflush_based_on("BOM")
def test_backflushed_based_on_for_multiple_batches(self):
item_code = "_Test Subcontracted FG Item 2"
make_item('Sub Contracted Raw Material 2', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
})
make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
raw_materials=["Sub Contracted Raw Material 2"])
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 500
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
rm_items = [
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.submit()
for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
make_new_batch(batch_id=batch, item_code=item_code)
pr = make_purchase_receipt(po.name)
# partial receipt
pr.get('items')[0].qty = 30
pr.get('items')[0].batch_no = "ABCD1"
purchase_order = po.name
purchase_order_item = po.items[0].name
for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
pr.append("items", {
"item_code": pr.get('items')[0].item_code,
"item_name": pr.get('items')[0].item_name,
"uom": pr.get('items')[0].uom,
"stock_uom": pr.get('items')[0].stock_uom,
"warehouse": pr.get('items')[0].warehouse,
"conversion_factor": pr.get('items')[0].conversion_factor,
"cost_center": pr.get('items')[0].cost_center,
"rate": pr.get('items')[0].rate,
"qty": qty,
"batch_no": batch_no,
"purchase_order": purchase_order,
"purchase_order_item": purchase_order_item
})
pr.submit()
pr1 = make_purchase_receipt(po.name)
pr1.get('items')[0].qty = 300
pr1.get('items')[0].batch_no = "ABCD1"
pr1.save()
pr_key = ("Sub Contracted Raw Material 2", po.name)
consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty))
update_backflush_based_on("BOM")
def test_supplied_qty_against_subcontracted_po(self):
item_code = "_Test Subcontracted FG Item 5"
make_item('Sub Contracted Raw Material 4', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
})
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 250
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True)
# Add same subcontracted items multiple times
po.append("items", {
"item_code": item_code,
"qty": order_qty,
"schedule_date": add_days(nowdate(), 1),
"warehouse": "_Test Warehouse - _TC"
})
po.set_missing_values()
po.submit()
# Material receipt entry for the raw materials which will be send to supplier
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 4", qty=500, basic_rate=100)
rm_items = [
{
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name
},
{
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[1].name
},
]
# Raw Materials transfer entry from stores to supplier's warehouse
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.submit()
po_doc = frappe.get_doc("Purchase Order", po.name)
for row in po_doc.supplied_items:
# Valid that whether transferred quantity is matching with supplied qty or not in the purchase order
self.assertEqual(row.supplied_qty, 250.0)
update_backflush_based_on("BOM")
def test_advance_payment_entry_unlink_against_purchase_order(self): def test_advance_payment_entry_unlink_against_purchase_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
frappe.db.set_value("Accounts Settings", "Accounts Settings", frappe.db.set_value("Accounts Settings", "Accounts Settings",
@ -839,27 +964,33 @@ def make_pr_against_po(po, received_qty=0):
pr.submit() pr.submit()
return pr return pr
def make_subcontracted_item(item_code): def make_subcontracted_item(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
if not frappe.db.exists('Item', item_code): args = frappe._dict(args)
make_item(item_code, {
if not frappe.db.exists('Item', args.item_code):
make_item(args.item_code, {
'is_stock_item': 1, 'is_stock_item': 1,
'is_sub_contracted_item': 1 'is_sub_contracted_item': 1,
'has_batch_no': args.get("has_batch_no") or 0
}) })
if not frappe.db.exists('Item', "Test Extra Item 1"): if not args.raw_materials:
make_item("Test Extra Item 1", { if not frappe.db.exists('Item', "Test Extra Item 1"):
'is_stock_item': 1, make_item("Test Extra Item 1", {
}) 'is_stock_item': 1,
})
if not frappe.db.exists('Item', "Test Extra Item 2"): if not frappe.db.exists('Item', "Test Extra Item 2"):
make_item("Test Extra Item 2", { make_item("Test Extra Item 2", {
'is_stock_item': 1, 'is_stock_item': 1,
}) })
if not frappe.db.get_value('BOM', {'item': item_code}, 'name'): args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
make_bom(item = item_code, raw_materials = ['_Test FG Item', 'Test Extra Item 1'])
if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
def update_backflush_based_on(based_on): def update_backflush_based_on(based_on):
doc = frappe.get_doc('Buying Settings') doc = frappe.get_doc('Buying Settings')

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _, msgprint from frappe import _, msgprint
from frappe.utils import flt,cint, cstr, getdate from frappe.utils import flt,cint, cstr, getdate
from six import iteritems
from erpnext.accounts.party import get_party_details from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
@ -112,8 +112,8 @@ class BuyingController(StockController):
"docstatus": 1 "docstatus": 1
})] })]
if self.is_return and len(not_cancelled_asset): if self.is_return and len(not_cancelled_asset):
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.".format(self.return_against)), frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
title=_("Not Allowed")) .format(self.return_against), title=_("Not Allowed"))
def get_asset_items(self): def get_asset_items(self):
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']: if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
@ -298,10 +298,10 @@ class BuyingController(StockController):
title=_("Limit Crossed")) title=_("Limit Crossed"))
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) # backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
for raw_material in transferred_raw_materials + non_stock_items: for raw_material in transferred_raw_materials + non_stock_items:
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order) rm_item_key = (raw_material.rm_item_code, item.purchase_order)
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0) consumed_qty = raw_material_data.get('qty', 0)
@ -330,8 +330,10 @@ class BuyingController(StockController):
set_serial_nos(raw_material, consumed_serial_nos, qty) set_serial_nos(raw_material, consumed_serial_nos, qty)
if raw_material.batch_nos: if raw_material.batch_nos:
backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {})
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
qty, transferred_batch_qty_map, backflushed_batch_qty_map) qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
for batch_data in batches_qty: for batch_data in batches_qty:
qty = batch_data['qty'] qty = batch_data['qty']
raw_material.batch_no = batch_data['batch'] raw_material.batch_no = batch_data['batch']
@ -343,6 +345,10 @@ class BuyingController(StockController):
rm = self.append('supplied_items', {}) rm = self.append('supplied_items', {})
rm.update(raw_material_data) rm.update(raw_material_data)
if not rm.main_item_code:
rm.main_item_code = fg_item_doc.item_code
rm.reference_name = fg_item_doc.name
rm.required_qty = qty rm.required_qty = qty
rm.consumed_qty = qty rm.consumed_qty = qty
@ -792,8 +798,8 @@ class BuyingController(StockController):
asset.set(field, None) asset.set(field, None)
asset.supplier = None asset.supplier = None
if asset.docstatus == 1 and delete_asset: if asset.docstatus == 1 and delete_asset:
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\ frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
Please cancel the it to continue.').format(frappe.utils.get_link_to_form('Asset', asset.name))) .format(frappe.utils.get_link_to_form('Asset', asset.name)))
asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_validate_update_after_submit = True
asset.flags.ignore_mandatory = True asset.flags.ignore_mandatory = True
@ -873,7 +879,7 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
AND se.purpose='Send to Subcontractor' AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s AND se.purchase_order = %s
AND IFNULL(sed.t_warehouse, '') != '' AND IFNULL(sed.t_warehouse, '') != ''
AND sed.subcontracted_item = %s AND IFNULL(sed.subcontracted_item, '') in ('', %s)
GROUP BY sed.item_code, sed.subcontracted_item GROUP BY sed.item_code, sed.subcontracted_item
""" """
raw_materials = frappe.db.multisql({ raw_materials = frappe.db.multisql({
@ -890,39 +896,49 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
return raw_materials return raw_materials
def get_backflushed_subcontracted_raw_materials(purchase_orders): def get_backflushed_subcontracted_raw_materials(purchase_orders):
common_query = """ purchase_receipts = frappe.get_all("Purchase Receipt Item",
SELECT fields = ["purchase_order", "item_code", "name", "parent"],
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key, filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
SUM(prsi.consumed_qty) AS qty,
{serial_no_concat_syntax} AS serial_nos,
{batch_no_concat_syntax} AS batch_nos
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
WHERE
pr.name = pri.parent
AND pr.name = prsi.parent
AND pri.purchase_order IN %s
AND pri.item_code = prsi.main_item_code
AND pr.docstatus = 1
GROUP BY prsi.rm_item_code, pri.purchase_order
"""
backflushed_raw_materials = frappe.db.multisql({ distinct_purchase_receipts = {}
'mariadb': common_query.format( for pr in purchase_receipts:
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)", key = (pr.purchase_order, pr.item_code, pr.parent)
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)" distinct_purchase_receipts.setdefault(key, []).append(pr.name)
),
'postgres': common_query.format(
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
)
}, (purchase_orders, ), as_dict=1)
backflushed_raw_materials_map = frappe._dict() backflushed_raw_materials_map = frappe._dict()
for item in backflushed_raw_materials: for args, references in iteritems(distinct_purchase_receipts):
backflushed_raw_materials_map.setdefault(item.item_key, item) purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references)
for data in purchase_receipt_supplied_items:
pr_key = (data.rm_item_code, args[0])
if pr_key not in backflushed_raw_materials_map:
backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({
"qty": 0.0,
"serial_no": [],
"batch_no": [],
"consumed_batch": {}
}))
row = backflushed_raw_materials_map.get(pr_key)
row.qty += data.consumed_qty
for field in ["serial_no", "batch_no"]:
if data.get(field):
row[field].append(data.get(field))
if data.get("batch_no"):
if data.get("batch_no") in row.consumed_batch:
row.consumed_batch[data.get("batch_no")] += data.consumed_qty
else:
row.consumed_batch[data.get("batch_no")] = data.consumed_qty
return backflushed_raw_materials_map return backflushed_raw_materials_map
def get_supplied_items(item_code, purchase_receipt, references):
return frappe.get_all("Purchase Receipt Item Supplied",
fields=["rm_item_code", "consumed_qty", "serial_no", "batch_no"],
filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)})
def get_asset_item_details(asset_items): def get_asset_item_details(asset_items):
asset_items_data = {} asset_items_data = {}
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"], for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
@ -1004,14 +1020,15 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
SELECT SELECT
sed.batch_no, sed.batch_no,
SUM(sed.qty) AS qty, SUM(sed.qty) AS qty,
sed.item_code sed.item_code,
sed.subcontracted_item
FROM `tabStock Entry` se,`tabStock Entry Detail` sed FROM `tabStock Entry` se,`tabStock Entry Detail` sed
WHERE WHERE
se.name = sed.parent se.name = sed.parent
AND se.docstatus=1 AND se.docstatus=1
AND se.purpose='Send to Subcontractor' AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s AND se.purchase_order = %s
AND sed.subcontracted_item = %s AND ifnull(sed.subcontracted_item, '') in ('', %s)
AND sed.batch_no IS NOT NULL AND sed.batch_no IS NOT NULL
GROUP BY GROUP BY
sed.batch_no, sed.batch_no,
@ -1019,8 +1036,10 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
""", (purchase_order, fg_item), as_dict=1) """, (purchase_order, fg_item), as_dict=1)
for batch_data in transferred_batches: for batch_data in transferred_batches:
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) key = ((batch_data.item_code, fg_item)
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
transferred_batch_qty_map.setdefault(key, {})
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
return transferred_batch_qty_map return transferred_batch_qty_map
@ -1057,10 +1076,11 @@ def get_backflushed_batch_qty_map(purchase_order, fg_item):
return backflushed_batch_qty_map return backflushed_batch_qty_map
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map): def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po):
# Returns available batches to be backflushed based on requirements # Returns available batches to be backflushed based on requirements
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {}) if not transferred_batches:
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
available_batches = [] available_batches = []

View File

@ -53,7 +53,7 @@ def execute():
# renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity # renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity
for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']: for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
if frappe.db.exists('Report', report): if frappe.db.exists('Report', report):
frappe.delete_doc('Report', report) frappe.delete_doc('Report', report, ignore_permissions=True)
def convert_to_seconds(value, unit): def convert_to_seconds(value, unit):

View File

@ -651,7 +651,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let child = frappe.model.add_child(me.frm.doc, "taxes"); let child = frappe.model.add_child(me.frm.doc, "taxes");
child.charge_type = "On Net Total"; child.charge_type = "On Net Total";
child.account_head = tax; child.account_head = tax;
child.rate = rate; child.rate = 0;
} }
}); });
} }

View File

@ -18,7 +18,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Review and Action", "label": "Review and Action",
"links": "[\n {\n \"description\": \"Quality Review\",\n \"label\": \"Quality Review\",\n \"name\": \"Quality Review\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Action\",\n \"label\": \"Quality Action\",\n \"name\": \"Quality Action\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"description\": \"Non Conformance\",\n \"label\": \"Non Conformance\",\n \"name\": \"Non Conformance\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Review\",\n \"label\": \"Quality Review\",\n \"name\": \"Quality Review\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Action\",\n \"label\": \"Quality Action\",\n \"name\": \"Quality Action\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Modules", "category": "Modules",
@ -29,11 +29,11 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Desk Page", "doctype": "Desk Page",
"extends_another_page": 0, "extends_another_page": 0,
"icon": "", "hide_custom": 0,
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Quality", "label": "Quality",
"modified": "2020-04-01 11:28:51.095012", "modified": "2020-10-27 16:28:54.138055",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality", "name": "Quality",
@ -47,6 +47,7 @@
"type": "DocType" "type": "DocType"
}, },
{ {
"doc_view": "Tree",
"label": "Quality Procedure", "label": "Quality Procedure",
"link_to": "Quality Procedure", "link_to": "Quality Procedure",
"type": "DocType" "type": "DocType"
@ -55,6 +56,33 @@
"label": "Quality Inspection", "label": "Quality Inspection",
"link_to": "Quality Inspection", "link_to": "Quality Inspection",
"type": "DocType" "type": "DocType"
},
{
"color": "#ff8989",
"doc_view": "",
"format": "{} Open",
"label": "Quality Review",
"link_to": "Quality Review",
"stats_filter": "{\"status\": \"Open\"}",
"type": "DocType"
},
{
"color": "#ff8989",
"doc_view": "",
"format": "{} Open",
"label": "Quality Action",
"link_to": "Quality Action",
"stats_filter": "{\"status\": \"Open\"}",
"type": "DocType"
},
{
"color": "#ff8989",
"doc_view": "",
"format": "{} Open",
"label": "Non Conformance",
"link_to": "Non Conformance",
"stats_filter": "{\"status\": \"Open\"}",
"type": "DocType"
} }
] ]
} }

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Non Conformance', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,118 @@
{
"actions": [],
"autoname": "format:QA-NC-{#####}",
"creation": "2020-10-21 14:49:50.350136",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"subject",
"procedure",
"process_owner",
"full_name",
"column_break_4",
"status",
"section_break_4",
"details",
"corrective_action",
"preventive_action"
],
"fields": [
{
"fieldname": "subject",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Subject",
"reqd": 1
},
{
"fieldname": "procedure",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Procedure",
"options": "Quality Procedure",
"reqd": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Open\nResolved\nCancelled",
"reqd": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
},
{
"fieldname": "details",
"fieldtype": "Text Editor",
"label": "Details"
},
{
"fetch_from": "procedure.process_owner",
"fieldname": "process_owner",
"fieldtype": "Data",
"label": "Process Owner",
"read_only": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fetch_from": "process_owner.full_name",
"fieldname": "full_name",
"fieldtype": "Data",
"hidden": 1,
"label": "Full Name"
},
{
"fieldname": "corrective_action",
"fieldtype": "Text",
"label": "Corrective Action"
},
{
"fieldname": "preventive_action",
"fieldtype": "Text",
"label": "Preventive Action"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-26 15:27:47.247814",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Non Conformance",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, 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 NonConformance(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestNonConformance(unittest.TestCase):
pass

View File

@ -2,32 +2,5 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Quality Action', { frappe.ui.form.on('Quality Action', {
onload: function(frm) {
frm.set_value("date", frappe.datetime.get_today());
frm.refresh();
},
document_name: function(frm){
frappe.call({
"method": "frappe.client.get",
args: {
doctype: frm.doc.document_type,
name: frm.doc.document_name
},
callback: function(data){
frm.fields_dict.resolutions.grid.remove_all();
let objectives = [];
if(frm.doc.document_type === "Quality Review"){
for(let i in data.message.reviews) objectives.push(data.message.reviews[i].review);
} else {
for(let j in data.message.parameters) objectives.push(data.message.parameters[j].feedback);
}
for (var objective in objectives){
frm.add_child("resolutions");
frm.fields_dict.resolutions.get_value()[objective].problem = objectives[objective];
}
frm.refresh();
}
});
},
}); });

View File

@ -1,32 +1,34 @@
{ {
"autoname": "format:ACTN-{#####}", "actions": [],
"autoname": "format:QA-ACT-{#####}",
"creation": "2018-10-02 11:40:43.666100", "creation": "2018-10-02 11:40:43.666100",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"corrective_preventive", "corrective_preventive",
"document_type", "review",
"goal", "feedback",
"status",
"cb_00", "cb_00",
"date", "date",
"document_name", "goal",
"procedure", "procedure",
"status",
"sb_00", "sb_00",
"resolutions" "resolutions"
], ],
"fields": [ "fields": [
{ {
"depends_on": "eval:doc.type == 'Quality Review'",
"fetch_from": "review.goal", "fetch_from": "review.goal",
"fieldname": "goal", "fieldname": "goal",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Goal", "label": "Goal",
"options": "Quality Goal", "options": "Quality Goal"
"read_only": 1
}, },
{ {
"default": "Today",
"fieldname": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
@ -34,34 +36,20 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:doc.type == 'Quality Review'",
"fieldname": "procedure", "fieldname": "procedure",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Procedure", "label": "Procedure",
"options": "Quality Procedure", "options": "Quality Procedure"
"read_only": 1
}, },
{ {
"default": "Open", "default": "Open",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Status", "label": "Status",
"options": "Open\nClosed" "options": "Open\nCompleted",
}, "read_only": 1
{
"fieldname": "document_name",
"fieldtype": "Dynamic Link",
"label": "Document Name",
"options": "document_type"
},
{
"fieldname": "document_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Document Type",
"options": "Quality Review\nQuality Feedback",
"reqd": 1
}, },
{ {
"default": "Corrective", "default": "Corrective",
@ -86,9 +74,24 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Resolutions", "label": "Resolutions",
"options": "Quality Action Resolution" "options": "Quality Action Resolution"
},
{
"fieldname": "review",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Review",
"options": "Quality Review"
},
{
"fieldname": "feedback",
"fieldtype": "Link",
"label": "Feedback",
"options": "Quality Feedback"
} }
], ],
"modified": "2019-05-28 13:10:44.092497", "index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-27 16:21:59.533937",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Action", "name": "Quality Action",

View File

@ -7,4 +7,5 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class QualityAction(Document): class QualityAction(Document):
pass def validate(self):
self.status = 'Open' if any([d.status=='Open' for d in self.resolutions]) else 'Completed'

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quality Action", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quality Actions
() => frappe.tests.make('Quality Actions', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -5,42 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from erpnext.quality_management.doctype.quality_procedure.test_quality_procedure import create_procedure
from erpnext.quality_management.doctype.quality_goal.test_quality_goal import create_unit
from erpnext.quality_management.doctype.quality_goal.test_quality_goal import create_goal
from erpnext.quality_management.doctype.quality_review.test_quality_review import create_review
class TestQualityAction(unittest.TestCase): class TestQualityAction(unittest.TestCase):
# quality action has no code
def test_quality_action(self): pass
create_procedure()
create_unit()
create_goal()
create_review()
test_create_action = create_action()
test_get_action = get_action()
self.assertEquals(test_create_action, test_get_action)
def create_action():
review = frappe.db.exists("Quality Review", {"goal": "GOAL-_Test Quality Goal"})
action = frappe.get_doc({
"doctype": "Quality Action",
"action": "Corrective",
"document_type": "Quality Review",
"document_name": review,
"date": frappe.utils.nowdate(),
"goal": "GOAL-_Test Quality Goal",
"procedure": "PRC-_Test Quality Procedure"
})
action_exist = frappe.db.exists("Quality Action", {"review": review})
if not action_exist:
action.insert()
return action.name
else:
return action_exist
def get_action():
review = frappe.db.exists("Quality Review", {"goal": "GOAL-_Test Quality Goal"})
return frappe.db.exists("Quality Action", {"document_name": review})

View File

@ -1,33 +1,54 @@
{ {
"actions": [],
"creation": "2019-05-26 20:36:44.337186", "creation": "2019-05-26 20:36:44.337186",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"problem", "problem",
"sb_00", "resolution",
"resolution" "status",
"responsible",
"completion_by"
], ],
"fields": [ "fields": [
{ {
"fieldname": "problem", "fieldname": "problem",
"fieldtype": "Long Text", "fieldtype": "Long Text",
"in_list_view": 1, "in_list_view": 1,
"label": "Review" "label": "Problem"
},
{
"fieldname": "sb_00",
"fieldtype": "Section Break"
}, },
{ {
"fieldname": "resolution", "fieldname": "resolution",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_list_view": 1, "in_list_view": 1,
"label": "Resolution" "label": "Resolution"
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Open\nCompleted"
},
{
"fieldname": "responsible",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Responsible",
"options": "User"
},
{
"fieldname": "completion_by",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Completion By"
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-28 13:09:50.435323", "links": [],
"modified": "2020-10-21 12:59:25.566682",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Action Resolution", "name": "Quality Action Resolution",

View File

@ -2,31 +2,9 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Quality Feedback', { frappe.ui.form.on('Quality Feedback', {
refresh: function(frm) {
frm.set_value("date", frappe.datetime.get_today());
},
template: function(frm) { template: function(frm) {
if (frm.doc.template) { if (frm.doc.template) {
frappe.call({ frm.call('set_parameters');
"method": "frappe.client.get",
args: {
doctype: "Quality Feedback Template",
name: frm.doc.template
},
callback: function(data) {
if (data && data.message) {
frm.fields_dict.parameters.grid.remove_all();
// fetch parameters from template and autofill
for (let template_parameter of data.message.parameters) {
let row = frm.add_child("parameters");
row.parameter = template_parameter.parameter;
}
frm.refresh();
}
}
});
} }
} }
}); });

View File

@ -1,16 +1,15 @@
{ {
"actions": [], "actions": [],
"autoname": "format:FDBK-{#####}", "autoname": "format:QA-FB-{#####}",
"creation": "2019-05-26 21:23:05.308379", "creation": "2019-05-26 21:23:05.308379",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"document_type",
"template", "template",
"cb_00", "cb_00",
"document_type",
"document_name", "document_name",
"date",
"sb_00", "sb_00",
"parameters" "parameters"
], ],
@ -18,6 +17,7 @@
{ {
"fieldname": "template", "fieldname": "template",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Template", "label": "Template",
"options": "Quality Feedback Template", "options": "Quality Feedback Template",
"reqd": 1 "reqd": 1
@ -26,13 +26,6 @@
"fieldname": "cb_00", "fieldname": "cb_00",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"read_only": 1
},
{ {
"fieldname": "sb_00", "fieldname": "sb_00",
"fieldtype": "Section Break" "fieldtype": "Section Break"
@ -47,6 +40,7 @@
{ {
"fieldname": "document_type", "fieldname": "document_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"label": "Type", "label": "Type",
"options": "User\nCustomer", "options": "User\nCustomer",
"reqd": 1 "reqd": 1
@ -54,13 +48,20 @@
{ {
"fieldname": "document_name", "fieldname": "document_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Feedback By", "label": "Feedback By",
"options": "document_type", "options": "document_type",
"reqd": 1 "reqd": 1
} }
], ],
"links": [], "links": [
"modified": "2020-07-03 15:50:58.589302", {
"group": "Actions",
"link_doctype": "Quality Action",
"link_fieldname": "feedback"
}
],
"modified": "2020-10-27 16:20:10.918544",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Feedback", "name": "Quality Feedback",

View File

@ -7,4 +7,17 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class QualityFeedback(Document): class QualityFeedback(Document):
pass def set_parameters(self):
if self.template and not getattr(self, 'parameters', []):
for d in frappe.get_doc('Quality Feedback Template', self.template).parameters:
self.append('parameters', dict(
parameter = d.parameter,
rating = 1
))
def validate(self):
if not self.document_name:
self.document_type ='User'
self.document_name = frappe.session.user
self.set_parameters()

View File

@ -5,49 +5,27 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from erpnext.quality_management.doctype.quality_feedback_template.test_quality_feedback_template import create_template
class TestQualityFeedback(unittest.TestCase): class TestQualityFeedback(unittest.TestCase):
def test_quality_feedback(self): def test_quality_feedback(self):
create_template() template = frappe.get_doc(dict(
test_create_feedback = create_feedback() doctype = 'Quality Feedback Template',
test_get_feedback = get_feedback() template = 'Test Template',
parameters = [
dict(parameter='Test Parameter 1'),
dict(parameter='Test Parameter 2')
]
)).insert()
self.assertEqual(test_create_feedback, test_get_feedback) feedback = frappe.get_doc(dict(
doctype = 'Quality Feedback',
template = template.name,
document_type = 'User',
document_name = frappe.session.user
)).insert()
def create_feedback(): self.assertEqual(template.parameters[0].parameter, feedback.parameters[0].parameter)
create_customer()
feedabck = frappe.get_doc({ feedback.delete()
"doctype": "Quality Feedback", template.delete()
"template": "TMPL-_Test Feedback Template",
"document_type": "Customer",
"document_name": "Quality Feedback Customer",
"date": frappe.utils.nowdate(),
"parameters": [
{
"parameter": "Test Parameter",
"rating": 3,
"feedback": "Test Feedback"
}
]
})
feedback_exists = frappe.db.exists("Quality Feedback", {"template": "TMPL-_Test Feedback Template"})
if not feedback_exists:
feedabck.insert()
return feedabck.name
else:
return feedback_exists
def get_feedback():
return frappe.db.exists("Quality Feedback", {"template": "TMPL-_Test Feedback Template"})
def create_customer():
if not frappe.db.exists("Customer", {"customer_name": "Quality Feedback Customer"}):
customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": "Quality Feedback Customer"
}).insert(ignore_permissions=True)

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-05-26 21:25:01.715807", "creation": "2019-05-26 21:25:01.715807",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -39,12 +40,13 @@
"fieldname": "feedback", "fieldname": "feedback",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_list_view": 1, "in_list_view": 1,
"label": "Feedback", "label": "Feedback"
"reqd": 1
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"modified": "2019-07-13 19:58:08.966141", "links": [],
"modified": "2020-10-27 17:28:12.033145",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Feedback Parameter", "name": "Quality Feedback Parameter",

View File

@ -1,13 +1,12 @@
{ {
"actions": [], "actions": [],
"autoname": "format:TMPL-{template}", "autoname": "field:template",
"creation": "2019-05-26 21:17:24.283061", "creation": "2019-05-26 21:17:24.283061",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"template", "template",
"cb_00",
"sb_00", "sb_00",
"parameters" "parameters"
], ],
@ -16,12 +15,9 @@
"fieldname": "template", "fieldname": "template",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Template", "label": "Template Name",
"reqd": 1 "reqd": 1,
}, "unique": 1
{
"fieldname": "cb_00",
"fieldtype": "Column Break"
}, },
{ {
"fieldname": "sb_00", "fieldname": "sb_00",
@ -35,8 +31,14 @@
"reqd": 1 "reqd": 1
} }
], ],
"links": [], "links": [
"modified": "2020-07-03 16:06:03.749415", {
"group": "Records",
"link_doctype": "Quality Feedback",
"link_fieldname": "template"
}
],
"modified": "2020-10-27 16:18:53.579688",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Feedback Template", "name": "Quality Feedback Template",

View File

@ -7,31 +7,4 @@ import frappe
import unittest import unittest
class TestQualityFeedbackTemplate(unittest.TestCase): class TestQualityFeedbackTemplate(unittest.TestCase):
pass
def test_quality_feedback_template(self):
test_create_template = create_template()
test_get_template = get_template()
self.assertEqual(test_create_template, test_get_template)
def create_template():
template = frappe.get_doc({
"doctype": "Quality Feedback Template",
"template": "_Test Feedback Template",
"parameters": [
{
"parameter": "Test Parameter"
}
]
})
template_exists = frappe.db.exists("Quality Feedback Template", {"template": "_Test Feedback Template"})
if not template_exists:
template.insert()
return template.name
else:
return template_exists
def get_template():
return frappe.db.exists("Quality Feedback Template", {"template": "_Test Feedback Template"})

View File

@ -2,7 +2,6 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Quality Goal', { frappe.ui.form.on('Quality Goal', {
refresh: function(frm) { // refresh: function(frm) {
frm.doc.created_by = frappe.session.user; // }
}
}); });

View File

@ -1,5 +1,6 @@
{ {
"autoname": "format:GOAL-{goal}", "actions": [],
"autoname": "field:goal",
"creation": "2018-10-02 12:17:41.727541", "creation": "2018-10-02 12:17:41.727541",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -7,27 +8,14 @@
"field_order": [ "field_order": [
"goal", "goal",
"frequency", "frequency",
"created_by",
"cb_00", "cb_00",
"procedure", "procedure",
"weekday", "weekday",
"quarter",
"date", "date",
"sb_00",
"revision",
"cb_01",
"revised_on",
"sb_01", "sb_01",
"objectives" "objectives"
], ],
"fields": [ "fields": [
{
"fieldname": "created_by",
"fieldtype": "Link",
"label": "Created By",
"options": "User",
"read_only": 1
},
{ {
"default": "None", "default": "None",
"fieldname": "frequency", "fieldname": "frequency",
@ -50,20 +38,6 @@
"label": "Date", "label": "Date",
"options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30" "options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30"
}, },
{
"default": "0",
"fieldname": "revision",
"fieldtype": "Int",
"label": "Revision",
"read_only": 1
},
{
"fieldname": "revised_on",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Revised On",
"read_only": 1
},
{ {
"depends_on": "eval:doc.frequency == 'Weekly';", "depends_on": "eval:doc.frequency == 'Weekly';",
"fieldname": "weekday", "fieldname": "weekday",
@ -75,15 +49,6 @@
"fieldname": "cb_00", "fieldname": "cb_00",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{
"fieldname": "sb_00",
"fieldtype": "Section Break",
"label": "Revision and Revised On"
},
{
"fieldname": "cb_01",
"fieldtype": "Column Break"
},
{ {
"fieldname": "sb_01", "fieldname": "sb_01",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -101,18 +66,17 @@
"label": "Goal", "label": "Goal",
"reqd": 1, "reqd": 1,
"unique": 1 "unique": 1
},
{
"default": "January-April-July-October",
"depends_on": "eval:doc.frequency == 'Quarterly';",
"fieldname": "quarter",
"fieldtype": "Select",
"label": "Quarter",
"options": "January-April-July-October",
"read_only": 1
} }
], ],
"modified": "2019-05-28 14:49:12.768863", "index_web_pages_for_search": 1,
"links": [
{
"group": "Review",
"link_doctype": "Quality Review",
"link_fieldname": "goal"
}
],
"modified": "2020-10-27 15:57:59.368605",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Goal", "name": "Quality Goal",

View File

@ -8,7 +8,5 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class QualityGoal(Document): class QualityGoal(Document):
def validate(self): def validate(self):
self.revision += 1 pass
self.revised_on = frappe.utils.today()

View File

@ -1,12 +0,0 @@
from frappe import _
def get_data():
return {
'fieldname': 'goal',
'transactions': [
{
'label': _('Review'),
'items': ['Quality Review']
}
]
}

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quality Goal", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quality Goal
() => frappe.tests.make('Quality Goal', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -8,44 +8,18 @@ import unittest
from erpnext.quality_management.doctype.quality_procedure.test_quality_procedure import create_procedure from erpnext.quality_management.doctype.quality_procedure.test_quality_procedure import create_procedure
class TestQualityGoal(unittest.TestCase): class TestQualityGoal(unittest.TestCase):
def test_quality_goal(self): def test_quality_goal(self):
create_procedure() # no code, just a basic sanity check
create_unit() goal = get_quality_goal()
test_create_goal = create_goal() self.assertTrue(goal)
test_get_goal = get_goal() goal.delete()
self.assertEquals(test_create_goal, test_get_goal) def get_quality_goal():
return frappe.get_doc(dict(
def create_goal(): doctype = 'Quality Goal',
goal = frappe.get_doc({ goal = 'Test Quality Module',
"doctype": "Quality Goal", frequency = 'Daily',
"goal": "_Test Quality Goal", objectives = [
"procedure": "PRC-_Test Quality Procedure", dict(objective = 'Check test cases', target='100', uom='Percent')
"objectives": [
{
"objective": "_Test Quality Objective",
"target": "4",
"uom": "_Test UOM"
}
] ]
}) )).insert()
goal_exist = frappe.db.exists("Quality Goal", {"goal": goal.goal})
if not goal_exist:
goal.insert()
return goal.name
else:
return goal_exist
def get_goal():
goal = frappe.db.exists("Quality Goal", "GOAL-_Test Quality Goal")
return goal
def create_unit():
unit = frappe.get_doc({
"doctype": "UOM",
"uom_name": "_Test UOM",
})
unit_exist = frappe.db.exists("UOM", unit.uom_name)
if not unit_exist:
unit.insert()

View File

@ -2,8 +2,5 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Quality Meeting', { frappe.ui.form.on('Quality Meeting', {
onload: function(frm){
frm.set_value("date", frappe.datetime.get_today());
frm.refresh();
}
}); });

View File

@ -1,28 +1,19 @@
{ {
"actions": [], "actions": [],
"autoname": "naming_series:", "autoname": "format:QA-MEET-{YY}-{MM}-{DD}",
"creation": "2018-10-15 16:25:41.548432", "creation": "2018-10-15 16:25:41.548432",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series",
"date",
"cb_00",
"status", "status",
"cb_00",
"sb_00", "sb_00",
"agenda", "agenda",
"sb_01", "sb_01",
"minutes" "minutes"
], ],
"fields": [ "fields": [
{
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"read_only": 1
},
{ {
"default": "Open", "default": "Open",
"fieldname": "status", "fieldname": "status",
@ -55,16 +46,11 @@
"fieldname": "sb_01", "fieldname": "sb_01",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Minutes" "label": "Minutes"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "MTNG-.YYYY.-.MM.-.DD.-"
} }
], ],
"index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-05-19 13:18:59.821740", "modified": "2020-10-27 16:36:45.657883",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Meeting", "name": "Quality Meeting",

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quality Meeting", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quality Meeting
() => frappe.tests.make('Quality Meeting', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -5,41 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from erpnext.quality_management.doctype.quality_review.test_quality_review import create_review
class TestQualityMeeting(unittest.TestCase): class TestQualityMeeting(unittest.TestCase):
def test_quality_meeting(self): # nothing to test
create_review() pass
test_create_meeting = create_meeting()
test_get_meeting = get_meeting()
self.assertEquals(test_create_meeting, test_get_meeting)
def create_meeting():
meeting = frappe.get_doc({
"doctype": "Quality Meeting",
"status": "Open",
"date": frappe.utils.nowdate(),
"agenda": [
{
"agenda": "Test Agenda"
}
],
"minutes": [
{
"document_type": "Quality Review",
"document_name": frappe.db.exists("Quality Review", {"goal": "GOAL-_Test Quality Goal"}),
"minute": "Test Minute"
}
]
})
meeting_exist = frappe.db.exists("Quality Meeting", {"date": frappe.utils.nowdate(), "status": "Open"})
if not meeting_exist:
meeting.insert()
return meeting.name
else:
return meeting_exist
def get_meeting():
meeting = frappe.db.exists("Quality Meeting", {"date": frappe.utils.nowdate(), "status": "Open"})
return meeting

View File

@ -1,19 +1,22 @@
{ {
"actions": [], "actions": [],
"allow_rename": 1, "allow_rename": 1,
"autoname": "format:PRC-{quality_procedure_name}", "autoname": "field:quality_procedure_name",
"creation": "2018-10-06 00:06:29.756804", "creation": "2018-10-06 00:06:29.756804",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"quality_procedure_name", "quality_procedure_name",
"process_owner",
"process_owner_full_name",
"section_break_3",
"processes",
"sb_00",
"parent_quality_procedure", "parent_quality_procedure",
"is_group", "is_group",
"sb_00",
"processes",
"lft",
"rgt", "rgt",
"lft",
"old_parent" "old_parent"
], ],
"fields": [ "fields": [
@ -34,14 +37,14 @@
"fieldname": "lft", "fieldname": "lft",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"label": "Lft", "label": "Left Index",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "rgt", "fieldname": "rgt",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"label": "Rgt", "label": "Right Index",
"read_only": 1 "read_only": 1
}, },
{ {
@ -54,7 +57,7 @@
{ {
"fieldname": "sb_00", "fieldname": "sb_00",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Processes" "label": "Parent"
}, },
{ {
"fieldname": "processes", "fieldname": "processes",
@ -67,12 +70,52 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Quality Procedure", "label": "Quality Procedure",
"reqd": 1 "reqd": 1,
"unique": 1
},
{
"fieldname": "process_owner",
"fieldtype": "Link",
"label": "Process Owner",
"options": "User"
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break"
},
{
"fetch_from": "process_owner.full_name",
"fieldname": "process_owner_full_name",
"fieldtype": "Data",
"hidden": 1,
"label": "Process Owner Full Name",
"print_hide": 1
} }
], ],
"is_tree": 1, "is_tree": 1,
"links": [], "links": [
"modified": "2020-10-13 11:46:07.744194", {
"group": "Reviews",
"link_doctype": "Quality Review",
"link_fieldname": "procedure"
},
{
"group": "Goals",
"link_doctype": "Quality Goal",
"link_fieldname": "procedure"
},
{
"group": "Actions",
"link_doctype": "Quality Action",
"link_fieldname": "procedure"
},
{
"group": "Actions",
"link_doctype": "Non Conformance",
"link_fieldname": "procedure"
}
],
"modified": "2020-10-26 15:25:39.316088",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Procedure", "name": "Quality Procedure",

View File

@ -14,69 +14,58 @@ class QualityProcedure(NestedSet):
self.check_for_incorrect_child() self.check_for_incorrect_child()
def on_update(self): def on_update(self):
NestedSet.on_update(self)
self.set_parent() self.set_parent()
def after_insert(self): def after_insert(self):
self.set_parent() self.set_parent()
#if Child is Added through Tree View.
# add child to parent if missing
if self.parent_quality_procedure: if self.parent_quality_procedure:
parent_quality_procedure = frappe.get_doc("Quality Procedure", self.parent_quality_procedure) parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
parent_quality_procedure.append("processes", {"procedure": self.name}) if not [d for d in parent.processes if d.procedure == self.name]:
parent_quality_procedure.save() parent.append("processes", {"procedure": self.name, "process_description": self.name})
parent.save()
def on_trash(self): def on_trash(self):
if self.parent_quality_procedure: # clear from child table (sub procedures)
doc = frappe.get_doc("Quality Procedure", self.parent_quality_procedure) frappe.db.sql('''update `tabQuality Procedure Process`
for process in doc.processes: set `procedure`='' where `procedure`=%s''', self.name)
if process.procedure == self.name: NestedSet.on_trash(self, allow_root_deletion=True)
doc.processes.remove(process)
doc.save(ignore_permissions=True)
flag_is_group = 0
doc.load_from_db()
for process in doc.processes:
flag_is_group = 1 if process.procedure else 0
doc.is_group = 0 if flag_is_group == 0 else 1
doc.save(ignore_permissions=True)
def set_parent(self): def set_parent(self):
rebuild_tree('Quality Procedure', 'parent_quality_procedure')
for process in self.processes: for process in self.processes:
# Set parent for only those children who don't have a parent # Set parent for only those children who don't have a parent
parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure") has_parent = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
if not parent_quality_procedure and process.procedure: if not has_parent and process.procedure:
frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name) frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
def check_for_incorrect_child(self): def check_for_incorrect_child(self):
for process in self.processes: for process in self.processes:
if process.procedure: if process.procedure:
self.is_group = 1
# Check if any child process belongs to another parent. # Check if any child process belongs to another parent.
parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure") parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
if parent_quality_procedure and parent_quality_procedure != self.name: if parent_quality_procedure and parent_quality_procedure != self.name:
frappe.throw(_("{0} already has a Parent Procedure {1}.".format(frappe.bold(process.procedure), frappe.bold(parent_quality_procedure))), frappe.throw(_("{0} already has a Parent Procedure {1}.").format(frappe.bold(process.procedure), frappe.bold(parent_quality_procedure)),
title=_("Invalid Child Procedure")) title=_("Invalid Child Procedure"))
self.is_group = 1
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False): def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
if parent is None or parent == "All Quality Procedures": if parent is None or parent == "All Quality Procedures":
parent = "" parent = ""
return frappe.db.sql(""" if parent:
select parent_procedure = frappe.get_doc('Quality Procedure', parent)
name as value, # return the list in order
is_group as expandable return [dict(
from value=d.procedure,
`tab{doctype}` expandable=frappe.db.get_value('Quality Procedure', d.procedure, 'is_group'))
where for d in parent_procedure.processes if d.procedure
ifnull(parent_quality_procedure, "")={parent} ]
""".format( else:
doctype = doctype, return frappe.get_all(doctype, fields=['name as value', 'is_group as expandable'],
parent=frappe.db.escape(parent) filters = dict(parent_quality_procedure = parent), order_by='name asc')
), as_dict=1)
@frappe.whitelist() @frappe.whitelist()
def add_node(): def add_node():
@ -88,4 +77,4 @@ def add_node():
if args.parent_quality_procedure == 'All Quality Procedures': if args.parent_quality_procedure == 'All Quality Procedures':
args.parent_quality_procedure = None args.parent_quality_procedure = None
frappe.get_doc(args).insert() return frappe.get_doc(args).insert()

View File

@ -1,20 +0,0 @@
from frappe import _
def get_data():
return {
'fieldname': 'procedure',
'transactions': [
{
'label': _('Goal'),
'items': ['Quality Goal']
},
{
'label': _('Review'),
'items': ['Quality Review']
},
{
'label': _('Action'),
'items': ['Quality Action']
}
],
}

View File

@ -15,7 +15,7 @@ frappe.treeview_settings["Quality Procedure"] = {
} }
}, },
], ],
breadcrumb: "Setup", breadcrumb: "Quality Management",
disable_add_node: true, disable_add_node: true,
root_label: "All Quality Procedures", root_label: "All Quality Procedures",
get_tree_root: false, get_tree_root: false,

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quality Procedure", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quality Procedure
() => frappe.tests.make('Quality Procedure', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -6,54 +6,45 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
class TestQualityProcedure(unittest.TestCase): from .quality_procedure import add_node
def test_quality_procedure(self):
test_create_procedure = create_procedure()
test_create_nested_procedure = create_nested_procedure()
test_get_procedure, test_get_nested_procedure = get_procedure()
self.assertEquals(test_create_procedure, test_get_procedure.get("name")) class TestQualityProcedure(unittest.TestCase):
self.assertEquals(test_create_nested_procedure, test_get_nested_procedure.get("name")) def test_add_node(self):
try:
procedure = frappe.get_doc(dict(
doctype = 'Quality Procedure',
quality_procedure_name = 'Test Procedure 1',
processes = [
dict(process_description = 'Test Step 1')
]
)).insert()
frappe.form_dict = dict(doctype = 'Quality Procedure', quality_procedure_name = 'Test Child 1',
parent_quality_procedure = procedure.name, cmd='test', is_root='false')
node = add_node()
procedure.reload()
self.assertEqual(procedure.is_group, 1)
# child row created
self.assertTrue([d for d in procedure.processes if d.procedure == node.name])
node.delete()
procedure.reload()
# child unset
self.assertFalse([d for d in procedure.processes if d.name == node.name])
finally:
procedure.delete()
def create_procedure(): def create_procedure():
procedure = frappe.get_doc({ return frappe.get_doc(dict(
"doctype": "Quality Procedure", doctype = 'Quality Procedure',
"quality_procedure_name": "_Test Quality Procedure", quality_procedure_name = 'Test Procedure 1',
"processes": [ is_group = 1,
{ processes = [
"process_description": "_Test Quality Procedure Table", dict(process_description = 'Test Step 1')
}
] ]
}) )).insert()
procedure_exist = frappe.db.exists("Quality Procedure", "PRC-_Test Quality Procedure")
if not procedure_exist:
procedure.insert()
return procedure.name
else:
return procedure_exist
def create_nested_procedure():
nested_procedure = frappe.get_doc({
"doctype": "Quality Procedure",
"quality_procedure_name": "_Test Nested Quality Procedure",
"processes": [
{
"procedure": "PRC-_Test Quality Procedure"
}
]
})
nested_procedure_exist = frappe.db.exists("Quality Procedure", "PRC-_Test Nested Quality Procedure")
if not nested_procedure_exist:
nested_procedure.insert()
return nested_procedure.name
else:
return nested_procedure_exist
def get_procedure():
procedure = frappe.get_doc("Quality Procedure", "PRC-_Test Quality Procedure")
nested_procedure = frappe.get_doc("Quality Procedure", "PRC-_Test Nested Quality Procedure")
return {"name": procedure.name}, {"name": nested_procedure.name, "parent_quality_procedure": nested_procedure.parent_quality_procedure}

View File

@ -10,6 +10,7 @@
], ],
"fields": [ "fields": [
{ {
"columns": 8,
"fieldname": "process_description", "fieldname": "process_description",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_list_view": 1, "in_list_view": 1,
@ -20,13 +21,14 @@
"fieldname": "procedure", "fieldname": "procedure",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Child Procedure", "label": "Sub Procedure",
"options": "Quality Procedure" "options": "Quality Procedure"
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-06-17 15:44:38.937915", "modified": "2020-10-27 13:55:11.252945",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Procedure Process", "name": "Quality Procedure Process",

View File

@ -2,9 +2,6 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Quality Review', { frappe.ui.form.on('Quality Review', {
onload: function(frm){
frm.set_value("date", frappe.datetime.get_today());
},
goal: function(frm) { goal: function(frm) {
frappe.call({ frappe.call({
"method": "frappe.client.get", "method": "frappe.client.get",

View File

@ -1,6 +1,6 @@
{ {
"actions": [], "actions": [],
"autoname": "format:REV-{#####}", "autoname": "format:QA-REV-{#####}",
"creation": "2018-10-02 11:45:16.301955", "creation": "2018-10-02 11:45:16.301955",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -18,6 +18,7 @@
], ],
"fields": [ "fields": [
{ {
"default": "Today",
"fieldname": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
@ -50,7 +51,7 @@
"collapsible": 1, "collapsible": 1,
"fieldname": "sb_01", "fieldname": "sb_01",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Additional Information" "label": "Notes"
}, },
{ {
"fieldname": "reviews", "fieldname": "reviews",
@ -63,7 +64,8 @@
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Status", "label": "Status",
"options": "Open\nClosed" "options": "Open\nPassed\nFailed",
"read_only": 1
}, },
{ {
"fieldname": "goal", "fieldname": "goal",
@ -74,8 +76,15 @@
"reqd": 1 "reqd": 1
} }
], ],
"links": [], "index_web_pages_for_search": 1,
"modified": "2020-02-01 10:59:38.933115", "links": [
{
"group": "Review",
"link_doctype": "Quality Action",
"link_fieldname": "review"
}
],
"modified": "2020-10-21 12:56:47.046172",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Review", "name": "Quality Review",
@ -120,5 +129,6 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "goal",
"track_changes": 1 "track_changes": 1
} }

View File

@ -7,7 +7,26 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class QualityReview(Document): class QualityReview(Document):
pass def validate(self):
# fetch targets from goal
if not self.reviews:
for d in frappe.get_doc('Quality Goal', self.goal).objectives:
self.append('reviews', dict(
objective = d.objective,
target = d.target,
uom = d.uom
))
self.set_status()
def set_status(self):
# if any child item is failed, fail the parent
if not len(self.reviews or []) or any([d.status=='Open' for d in self.reviews]):
self.status = 'Open'
elif any([d.status=='Failed' for d in self.reviews]):
self.status = 'Failed'
else:
self.status = 'Passed'
def review(): def review():
day = frappe.utils.getdate().day day = frappe.utils.getdate().day
@ -24,7 +43,7 @@ def review():
elif goal.frequency == 'Monthly' and goal.date == str(day): elif goal.frequency == 'Monthly' and goal.date == str(day):
create_review(goal.name) create_review(goal.name)
elif goal.frequency == 'Quarterly' and goal.data == str(day) and get_quarter(month): elif goal.frequency == 'Quarterly' and day==1 and get_quarter(month):
create_review(goal.name) create_review(goal.name)
def create_review(goal): def create_review(goal):
@ -36,15 +55,6 @@ def create_review(goal):
"date": frappe.utils.getdate() "date": frappe.utils.getdate()
}) })
for objective in goal.objectives:
review.append("reviews",
{
"objective": objective.objective,
"target": objective.target,
"uom": objective.uom
}
)
review.insert(ignore_permissions=True) review.insert(ignore_permissions=True)
def get_quarter(month): def get_quarter(month):

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Performance Monitoring", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Performance Monitoring
() => frappe.tests.make('Performance Monitoring', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -5,42 +5,18 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from erpnext.quality_management.doctype.quality_procedure.test_quality_procedure import create_procedure
from erpnext.quality_management.doctype.quality_goal.test_quality_goal import create_unit from ..quality_goal.test_quality_goal import get_quality_goal
from erpnext.quality_management.doctype.quality_goal.test_quality_goal import create_goal from .quality_review import review
class TestQualityReview(unittest.TestCase): class TestQualityReview(unittest.TestCase):
def test_review_creation(self):
quality_goal = get_quality_goal()
review()
def test_quality_review(self): # check if review exists
create_procedure() quality_review = frappe.get_doc('Quality Review', dict(goal = quality_goal.name))
create_unit() self.assertEqual(quality_goal.objectives[0].target, quality_review.reviews[0].target)
create_goal() quality_review.delete()
test_create_review = create_review()
test_get_review = get_review()
self.assertEquals(test_create_review, test_get_review)
def create_review(): quality_goal.delete()
review = frappe.get_doc({
"doctype": "Quality Review",
"goal": "GOAL-_Test Quality Goal",
"procedure": "PRC-_Test Quality Procedure",
"date": frappe.utils.nowdate(),
"reviews": [
{
"objective": "_Test Quality Objective",
"target": "100",
"uom": "_Test UOM",
"review": "Test Review"
}
]
})
review_exist = frappe.db.exists("Quality Review", {"goal": "GOAL-_Test Quality Goal"})
if not review_exist:
review.insert(ignore_permissions=True)
return review.name
else:
return review_exist
def get_review():
review = frappe.db.exists("Quality Review", {"goal": "GOAL-_Test Quality Goal"})
return review

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-05-26 15:17:44.796958", "creation": "2019-05-26 15:17:44.796958",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -9,10 +10,12 @@
"target", "target",
"uom", "uom",
"sb_00", "sb_00",
"status",
"review" "review"
], ],
"fields": [ "fields": [
{ {
"columns": 3,
"fieldname": "objective", "fieldname": "objective",
"fieldtype": "Text", "fieldtype": "Text",
"in_list_view": 1, "in_list_view": 1,
@ -20,6 +23,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"columns": 2,
"fieldname": "target", "fieldname": "target",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
@ -27,6 +31,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"columns": 1,
"fetch_from": "target_unit", "fetch_from": "target_unit",
"fieldname": "uom", "fieldname": "uom",
"fieldtype": "Link", "fieldtype": "Link",
@ -49,10 +54,20 @@
{ {
"fieldname": "cb_00", "fieldname": "cb_00",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Open\nPassed\nFailed"
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-26 16:14:12.586128", "links": [],
"modified": "2020-10-27 16:28:20.908637",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Quality Management", "module": "Quality Management",
"name": "Quality Review Objective", "name": "Quality Review Objective",

View File

@ -441,6 +441,7 @@ class TestSalesOrder(unittest.TestCase):
def test_update_child_qty_rate_with_workflow(self): def test_update_child_qty_rate_with_workflow(self):
from frappe.model.workflow import apply_workflow from frappe.model.workflow import apply_workflow
frappe.set_user("Administrator")
workflow = make_sales_order_workflow() workflow = make_sales_order_workflow()
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1) so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
apply_workflow(so, 'Approve') apply_workflow(so, 'Approve')

View File

@ -256,3 +256,18 @@ class TestBatch(unittest.TestCase):
batch.insert() batch.insert()
return batch return batch
def make_new_batch(**args):
args = frappe._dict(args)
try:
batch = frappe.get_doc({
"doctype": "Batch",
"batch_id": args.batch_id,
"item": args.item_code,
}).insert()
except frappe.DuplicateEntryError:
batch = frappe.get_doc("Batch", args.batch_id)
return batch

View File

@ -174,7 +174,7 @@ class TestPurchaseReceipt(unittest.TestCase):
update_backflush_based_on("Material Transferred for Subcontract") update_backflush_based_on("Material Transferred for Subcontract")
item_code = "_Test Subcontracted FG Item 1" item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code) make_subcontracted_item(item_code=item_code)
po = create_purchase_order(item_code=item_code, qty=1, po = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
@ -717,6 +717,66 @@ class TestPurchaseReceipt(unittest.TestCase):
# Allowed to submit for other company's PR # Allowed to submit for other company's PR
self.assertEqual(pr.docstatus, 1) self.assertEqual(pr.docstatus, 1)
def test_subcontracted_pr_for_multi_transfer_batches(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry, make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on,
create_purchase_order)
update_backflush_based_on("Material Transferred for Subcontract")
item_code = "_Test Subcontracted FG Item 3"
make_item('Sub Contracted Raw Material 3', {
'is_stock_item': 1,
'is_sub_contracted_item': 1,
'has_batch_no': 1,
'create_new_batch': 1
})
create_subcontracted_item(item_code=item_code, has_batch_no=1,
raw_materials=["Sub Contracted Raw Material 3"])
order_qty = 500
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
ste1=make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 3", qty=300, basic_rate=100)
ste2=make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 3", qty=200, basic_rate=100)
transferred_batch = {
ste1.items[0].batch_no : 300,
ste2.items[0].batch_no : 200
}
rm_items = [
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
"qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name},
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
"qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name}
]
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
self.assertEqual(len(se.items), 2)
se.items[0].batch_no = ste1.items[0].batch_no
se.items[1].batch_no = ste2.items[0].batch_no
se.submit()
supplied_qty = frappe.db.get_value("Purchase Order Item Supplied",
{"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, "supplied_qty")
self.assertEqual(supplied_qty, 500.00)
pr = make_purchase_receipt(po.name)
pr.save()
self.assertEqual(len(pr.supplied_items), 2)
for row in pr.supplied_items:
self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty)
update_backflush_based_on("BOM")
def get_sl_entries(voucher_type, voucher_no): def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
@ -858,6 +918,33 @@ def make_purchase_receipt(**args):
pr.submit() pr.submit()
return pr return pr
def create_subcontracted_item(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
args = frappe._dict(args)
if not frappe.db.exists('Item', args.item_code):
make_item(args.item_code, {
'is_stock_item': 1,
'is_sub_contracted_item': 1,
'has_batch_no': args.get("has_batch_no") or 0
})
if not args.raw_materials:
if not frappe.db.exists('Item', "Test Extra Item 1"):
make_item("Test Extra Item 1", {
'is_stock_item': 1,
})
if not frappe.db.exists('Item', "Test Extra Item 2"):
make_item("Test Extra Item 2", {
'is_stock_item': 1,
})
args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
test_dependencies = ["BOM", "Item Price", "Location"] test_dependencies = ["BOM", "Item Price", "Location"]
test_records = frappe.get_test_records('Purchase Receipt') test_records = frappe.get_test_records('Purchase Receipt')

View File

@ -236,7 +236,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-09-12 16:11:31.910508", "modified": "2020-10-21 13:03:11.938072",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Quality Inspection", "name": "Quality Inspection",
@ -257,7 +257,6 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"search_fields": "item_code, report_date, reference_name", "search_fields": "item_code, report_date, reference_name",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",

View File

@ -343,10 +343,11 @@ def validate_material_transfer_entry(sle_doc):
def validate_so_serial_no(sr, sales_order): def validate_so_serial_no(sr, sales_order):
if not sr.sales_order or sr.sales_order!= sales_order: if not sr.sales_order or sr.sales_order!= sales_order:
msg = _("Sales Order {0} has reservation for item {1}") msg = (_("Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}.")
msg += _(", you can only deliver reserved {1} against {0}.") .format(sales_order, sr.item_code))
msg += _(" Serial No {2} cannot be delivered")
frappe.throw(msg.format(sales_order, sr.item_code, sr.name)) frappe.throw(_("""{0} Serial No {1} cannot be delivered""")
.format(msg, sr.name))
def has_duplicate_serial_no(sn, sle): def has_duplicate_serial_no(sn, sle):
if (sn.warehouse and not sle.skip_serial_no_validaiton if (sn.warehouse and not sle.skip_serial_no_validaiton
@ -449,6 +450,9 @@ def get_item_details(item_code):
from tabItem where name=%s""", item_code, as_dict=True)[0] from tabItem where name=%s""", item_code, as_dict=True)[0]
def get_serial_nos(serial_no): def get_serial_nos(serial_no):
if isinstance(serial_no, list):
return serial_no
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
if s.strip()] if s.strip()]
@ -572,8 +576,8 @@ def get_pos_reserved_serial_nos(filters):
pos_transacted_sr_nos = frappe.db.sql("""select item.serial_no as serial_no pos_transacted_sr_nos = frappe.db.sql("""select item.serial_no as serial_no
from `tabPOS Invoice` p, `tabPOS Invoice Item` item from `tabPOS Invoice` p, `tabPOS Invoice Item` item
where p.name = item.parent where p.name = item.parent
and p.consolidated_invoice is NULL and p.consolidated_invoice is NULL
and p.docstatus = 1 and p.docstatus = 1
and item.docstatus = 1 and item.docstatus = 1
and item.item_code = %(item_code)s and item.item_code = %(item_code)s
@ -605,7 +609,7 @@ def fetch_serial_numbers(filters, qty, do_not_include=[]):
SELECT sr.name FROM `tabSerial No` sr {batch_join_selection} SELECT sr.name FROM `tabSerial No` sr {batch_join_selection}
WHERE WHERE
sr.name not in ({excluded_sr_nos}) AND sr.name not in ({excluded_sr_nos}) AND
sr.item_code = %(item_code)s AND sr.item_code = %(item_code)s AND
sr.warehouse = %(warehouse)s AND sr.warehouse = %(warehouse)s AND
ifnull(sr.sales_invoice,'') = '' AND ifnull(sr.sales_invoice,'') = '' AND
ifnull(sr.delivery_document_no, '') = '' ifnull(sr.delivery_document_no, '') = ''
@ -620,5 +624,5 @@ def fetch_serial_numbers(filters, qty, do_not_include=[]):
batch_join_selection=batch_join_selection, batch_join_selection=batch_join_selection,
batch_no_condition=batch_no_condition batch_no_condition=batch_no_condition
), filters, as_dict=1) ), filters, as_dict=1)
return serial_numbers return serial_numbers

View File

@ -571,8 +571,9 @@ class StockEntry(StockController):
qty_allowance = flt(frappe.db.get_single_value("Buying Settings", qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
"over_transfer_allowance")) "over_transfer_allowance"))
if (self.purpose == "Send to Subcontractor" and self.purchase_order and if not (self.purpose == "Send to Subcontractor" and self.purchase_order): return
backflush_raw_materials_based_on == 'BOM'):
if (backflush_raw_materials_based_on == 'BOM'):
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items: for se_item in self.items:
item_code = se_item.original_item or se_item.item_code item_code = se_item.original_item or se_item.item_code
@ -609,6 +610,11 @@ class StockEntry(StockController):
if flt(total_supplied, precision) > flt(total_allowed, precision): if flt(total_supplied, precision) > flt(total_allowed, precision):
frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}") frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}")
.format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order)) .format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order))
elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
for row in self.items:
if not row.subcontracted_item:
frappe.throw(_("Row {0}: Subcontracted Item is mandatory for the raw material {1}")
.format(row.idx, frappe.bold(row.item_code)))
def validate_bom(self): def validate_bom(self):
for d in self.get('items'): for d in self.get('items'):
@ -817,6 +823,13 @@ class StockEntry(StockController):
ret.get('has_batch_no') and not args.get('batch_no')): ret.get('has_batch_no') and not args.get('batch_no')):
args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty']) args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
if self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get('item_code'):
subcontract_items = frappe.get_all("Purchase Order Item Supplied",
{"parent": self.purchase_order, "rm_item_code": args.get('item_code')}, "main_item_code")
if subcontract_items and len(subcontract_items) == 1:
ret["subcontracted_item"] = subcontract_items[0].main_item_code
return ret return ret
def set_items_for_stock_in(self): def set_items_for_stock_in(self):
@ -1288,9 +1301,16 @@ class StockEntry(StockController):
#Update Supplied Qty in PO Supplied Items #Update Supplied Qty in PO Supplied Items
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed SET
WHERE pos.name = sed.po_detail and sed.docstatus = 1) pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0)
WHERE pos.docstatus = 1 and pos.parent = %s""", self.purchase_order) FROM
`tabStock Entry Detail` sed, `tabStock Entry` se
WHERE
pos.name = sed.po_detail AND pos.rm_item_code = sed.item_code
AND pos.parent = se.purchase_order AND sed.docstatus = 1
AND se.name = sed.parent and se.purchase_order = %(po)s
), 0)
WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
#Update reserved sub contracted quantity in bin based on Supplied Item Details and #Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"): for d in self.get("items"):

View File

@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-03-29 18:22:12", "creation": "2013-03-29 18:22:12",
"doctype": "DocType", "doctype": "DocType",
@ -16,6 +15,7 @@
"item_code", "item_code",
"col_break2", "col_break2",
"item_name", "item_name",
"subcontracted_item",
"section_break_8", "section_break_8",
"description", "description",
"column_break_10", "column_break_10",
@ -57,7 +57,6 @@
"material_request", "material_request",
"material_request_item", "material_request_item",
"original_item", "original_item",
"subcontracted_item",
"reference_section", "reference_section",
"against_stock_entry", "against_stock_entry",
"ste_detail", "ste_detail",
@ -415,6 +414,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:parent.purpose == 'Send to Subcontractor'",
"fieldname": "subcontracted_item", "fieldname": "subcontracted_item",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Subcontracted Item", "label": "Subcontracted Item",
@ -504,7 +504,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-22 17:55:03.384138", "modified": "2020-09-23 17:55:03.384138",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",

View File

@ -6,10 +6,17 @@ import frappe
from frappe import _ from frappe import _
def execute(filters=None): def execute(filters=None):
validate_warehouse(filters)
columns = get_columns() columns = get_columns()
data = get_data(filters.warehouse) data = get_data(filters.warehouse)
return columns, data return columns, data
def validate_warehouse(filters):
company = filters.company
warehouse = filters.warehouse
if not frappe.db.exists("Warehouse", {"name": warehouse, "company": company}):
frappe.throw(_("Warehouse: {0} does not belong to {1}").format(warehouse, company))
def get_columns(): def get_columns():
columns = [ columns = [
{ {

View File

@ -45,7 +45,7 @@
<p class='lead'>{{ education_settings.description }}</p> <p class='lead'>{{ education_settings.description }}</p>
<p class="mt-4"> <p class="mt-4">
{% if frappe.session.user == 'Guest' %} {% if frappe.session.user == 'Guest' %}
<a class="btn btn-primary btn-lg" href="'/login#signup'">{{_('Sign Up')}}</a> <a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
{% endif %} {% endif %}
</p> </p>
</div> </div>
@ -62,4 +62,4 @@
</div> </div>
</div> </div>
</section> </section>
{% endblock %} {% endblock %}