Merge branch 'develop' into purchase-invoice-payment-terms

This commit is contained in:
Marica 2020-11-05 12:25:38 +05:30 committed by GitHub
commit 6d2bcd2404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 8051 additions and 1643 deletions

View File

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

View File

@ -19,7 +19,7 @@ class AccountingDimension(Document):
def validate(self):
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)
frappe.throw(msg)

View File

@ -158,8 +158,11 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
month = now_datetime().month
if month > 10:
month = 10
for i in range(now_datetime().month):
for i in range(month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
@ -177,8 +180,11 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project")
month = now_datetime().month
if month > 10:
month = 10
for i in range(now_datetime().month):
for i in range(month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")

View File

@ -14,6 +14,7 @@
"column_break_9",
"update_stock",
"ignore_pricing_rule",
"hide_unavailable_items",
"warehouse",
"campaign",
"company_address",
@ -307,13 +308,19 @@
"fieldtype": "Check",
"label": "Update Stock",
"read_only": 1
},
{
"default": "0",
"fieldname": "hide_unavailable_items",
"fieldtype": "Check",
"label": "Hide Unavailable Items"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-20 13:16:50.665081",
"modified": "2020-10-29 13:18:38.795925",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -504,10 +504,10 @@
},
{
"default": "0",
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"fieldname": "apply_discount_on_rate",
"fieldtype": "Check",
"label": "Apply Discount on Rate"
"label": "Apply Discount on Discounted Rate"
},
{
"default": "0",
@ -563,7 +563,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2020-08-26 12:24:44.740734",
"modified": "2020-10-28 16:53:14.416172",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -60,6 +60,15 @@ class PricingRule(Document):
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
if self.apply_discount_on_rate:
if not self.priority:
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
if self.priority and cint(self.priority) == 1:
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
item_details = frappe._dict({
"doctype": args.doctype,
"has_margin": False,
"name": args.name,
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get('child_docname'),
"discount_percentage_on_rate": [],
"discount_amount_on_rate": []
"child_docname": args.get('child_docname')
})
if args.ignore_pricing_rule or not args.item_code:
@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
else:
get_product_discount_rule(pricing_rule, item_details, args, doc)
if not item_details.get("has_margin"):
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
item_details.has_pricing_rule = 1
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
@ -330,13 +342,11 @@ def get_pricing_rule_details(args, pricing_rule):
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
or (pricing_rule.margin_type == 'Percentage')):
item_details.margin_type = pricing_rule.margin_type
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
else:
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
item_details.has_margin = True
if pricing_rule.rate_or_discount == 'Rate':
pricing_rule_rate = 0.0
@ -351,9 +361,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.rate_or_discount != apply_on: continue
field = frappe.scrub(apply_on)
if pricing_rule.apply_discount_on_rate:
discount_field = "{0}_on_rate".format(field)
item_details[discount_field].append(pricing_rule.get(field, 0))
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
else:
if field not in item_details:
item_details.setdefault(field, 0)
@ -361,14 +371,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
item_details[field] += (pricing_rule.get(field, 0)
if pricing_rule else args.get(field, 0))
def set_discount_amount(rate, item_details):
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
for d in item_details.get(field):
dis_amount = (rate * d / 100
if field == 'discount_percentage_on_rate' else d)
rate -= dis_amount
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
get_pricing_rule_items)

View File

@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_cumulative_pricing_rule(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
test_record = {
@ -429,34 +429,61 @@ class TestPricingRule(unittest.TestCase):
details = get_item_details(args)
self.assertTrue(details)
def test_pricing_rule_for_condition(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(selling=1, margin_type="Percentage", \
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
# Incorrect Customer and Correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 900)
def test_multiple_pricing_rules(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 30)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 28)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def make_pricing_rule(**args):
args = frappe._dict(args)
@ -468,6 +495,7 @@ def make_pricing_rule(**args):
"applicable_for": args.applicable_for,
"selling": args.selling or 0,
"currency": "USD",
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0,
"min_qty": args.min_qty or 0.0,
"max_qty": args.max_qty or 0.0,
@ -476,9 +504,13 @@ def make_pricing_rule(**args):
"rate": args.rate or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or ''
"condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})
if args.get("priority"):
doc.priority = args.get("priority")
apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
doc.append(child_table.get(doc.apply_on), {

View File

@ -14,9 +14,8 @@ import frappe
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
from frappe import _, throw
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
from frappe import _, bold
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None):
if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules):
pricing_rules = sorted_by_priority(pricing_rules)
for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule:
@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None):
return rules
def sorted_by_priority(pricing_rules):
# If more than one pricing rules, then sort by priority
pricing_rules_list = []
pricing_rule_dict = {}
for pricing_rule in pricing_rules:
if not pricing_rule.get("priority"): continue
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for key in sorted(pricing_rule_dict):
pricing_rules_list.append(pricing_rule_dict.get(key))
return pricing_rules_list or pricing_rules
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
if doc:
@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
fieldname = field
if fieldname:
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
if fieldname in ['min_amt', 'max_amt']:
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
bold(item_code), bold(args.rule_description)))
frappe.msgprint(msg)

View File

@ -237,7 +237,7 @@ class TestSubscription(unittest.TestCase):
subscription.party_type = 'Customer'
subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.start_date = '2018-01-01'
subscription.start_date = add_days(nowdate(), -1000)
subscription.insert()
subscription.process() # generate first invoice

View File

@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field):
FROM
`tabPOS Invoice` p {from_sales_invoice_payment}
WHERE
p.docstatus = 1 and
{group_by_mop_condition}
{conditions}
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--\"}",
"group_by_type": "Count",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:53:33.211371",
"modified": "2020-10-28 23:15:58.432189",
"modified_by": "Administrator",
"module": "Assets",
"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()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:39:32.429240",
"modified": "2020-10-28 23:16:16.939070",
"modified_by": "Administrator",
"module": "Assets",
"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()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:42:44.912551",
"modified": "2020-10-28 23:16:07.883312",
"modified_by": "Administrator",
"module": "Assets",
"name": "Location-wise Asset Value",

View File

@ -55,6 +55,7 @@
"fieldtype": "Date",
"in_list_view": 1,
"label": "Depreciation Posting Date",
"mandatory_depends_on": "eval:parent.doctype == 'Asset'",
"reqd": 1
},
{
@ -86,7 +87,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-09-16 12:11:30.631788",
"modified": "2020-10-30 15:22:29.119868",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",

View File

@ -876,7 +876,7 @@ class TestPurchaseOrder(unittest.TestCase):
},
{
"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
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"
},
]
@ -885,6 +885,10 @@ class TestPurchaseOrder(unittest.TestCase):
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.submit()
# Test po_detail field has value or not
for item_row in se.items:
self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name)
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

View File

@ -304,7 +304,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
{
"fieldtype": "Select", "label": __("Get Suppliers By"),
"fieldname": "search_type",
"options": ["Tag","Supplier Group"],
"options": ["Supplier Group", "Tag"],
"reqd": 1,
onchange() {
if(dialog.get_value('search_type') == 'Tag'){

View File

@ -21,9 +21,9 @@
"link_to_mrs",
"supplier_response_section",
"salutation",
"email_template",
"col_break_email_1",
"subject",
"col_break_email_1",
"email_template",
"preview",
"sec_break_email_2",
"message_for_supplier",
@ -260,7 +260,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-10-16 17:49:09.561929",
"modified": "2020-11-04 22:04:29.017134",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",

View File

@ -5,14 +5,14 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"send_email",
"email_sent",
"supplier",
"contact",
"quote_status",
"column_break_3",
"supplier_name",
"email_id"
"email_id",
"send_email",
"email_sent"
],
"fields": [
{
@ -87,7 +87,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-10-16 12:23:41.769820",
"modified": "2020-11-04 22:01:43.832942",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Supplier",

View File

@ -263,6 +263,7 @@ class AccountsController(TransactionBase):
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = []
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@ -301,6 +302,7 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate)
@ -322,6 +324,9 @@ class AccountsController(TransactionBase):
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
item.rate = pricing_rule_args.get("rate")
elif pricing_rule_args.get('free_item_data'):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
@ -335,6 +340,18 @@ class AccountsController(TransactionBase):
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
def set_pricing_rule_details(self, item_row, args):
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
if not pricing_rules: return
for pricing_rule in pricing_rules:
self.append("pricing_rules", {
"pricing_rule": pricing_rule,
"item_code": item_row.item_code,
"child_docname": item_row.name,
"rule_applied": True
})
def set_taxes(self):
if not self.meta.get_field("taxes"):
return

View File

@ -301,7 +301,7 @@ class BuyingController(StockController):
# 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:
rm_item_key = (raw_material.rm_item_code, item.purchase_order)
rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order)
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0)
@ -910,7 +910,7 @@ def get_backflushed_subcontracted_raw_materials(purchase_orders):
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])
pr_key = (data.rm_item_code, data.main_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,
@ -936,7 +936,7 @@ def get_backflushed_subcontracted_raw_materials(purchase_orders):
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"],
fields=["rm_item_code", "main_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):

View File

@ -608,16 +608,19 @@ class calculate_taxes_and_totals(object):
base_rate_with_margin = 0.0
if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule:
has_margin = False
for d in get_applied_pricing_rules(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
or (pricing_rule.margin_type == 'Percentage'):
item.margin_type = pricing_rule.margin_type
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
else:
item.margin_type = None
item.margin_rate_or_amount = 0.0
has_margin = True
if not has_margin:
item.margin_type = None
item.margin_rate_or_amount = 0.0
if item.margin_type and item.margin_rate_or_amount:
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100

View File

@ -75,6 +75,6 @@ class StudentAttendance(Document):
})
if attendance_record:
record = get_link_to_form('Attendance Record', attendance_record)
record = get_link_to_form('Student Attendance', attendance_record)
frappe.throw(_('Student Attendance record {0} already exists against the Student {1}')
.format(record, frappe.bold(self.student)), title=_('Duplicate Entry'))

View File

@ -8,7 +8,7 @@
{
"hidden": 0,
"label": "Payments",
"links": "[\n {\n \"description\": \"GoCardless payment gateway settings\",\n \"label\": \"GoCardless Settings\",\n \"name\": \"GoCardless Settings\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"description\": \"GoCardless payment gateway settings\",\n \"label\": \"GoCardless Settings\",\n \"name\": \"GoCardless Settings\",\n \"type\": \"doctype\"\n }, {\n \"description\": \"M-Pesa payment gateway settings\",\n \"label\": \"M-Pesa Settings\",\n \"name\": \"Mpesa Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@ -29,7 +29,7 @@
"idx": 0,
"is_standard": 1,
"label": "ERPNext Integrations",
"modified": "2020-08-23 16:30:51.494655",
"modified": "2020-10-29 19:54:46.228222",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",

View File

@ -9,11 +9,12 @@ frappe.ui.form.on('Mpesa Settings', {
refresh: function(frm) {
frappe.realtime.on("refresh_mpesa_dashboard", function(){
frm.reload_doc();
frm.events.setup_account_balance_html(frm);
});
},
get_account_balance: function(frm) {
if (!frm.initiator_name && !frm.security_credentials) {
if (!frm.doc.initiator_name && !frm.doc.security_credential) {
frappe.throw(__("Please set the initiator name and the security credential"));
}
frappe.call({

View File

@ -147,7 +147,7 @@ def get_account_balance(request_payload):
return response
except Exception:
frappe.log_error(title=_("Account Balance Processing Error"))
frappe.throw(title=_("Error"), message=_("Please check your configuration and try again"))
frappe.throw(_("Please check your configuration and try again"), title=_("Error"))
@frappe.whitelist(allow_guest=True)
def process_balance_info(**kwargs):
@ -173,7 +173,8 @@ def process_balance_info(**kwargs):
ref_doc.db_set("account_balance", balance_info)
request.handle_success(account_balance_response)
frappe.publish_realtime("refresh_mpesa_dashboard")
frappe.publish_realtime("refresh_mpesa_dashboard", doctype="Mpesa Settings",
docname=transaction_data.reference_docname, user=transaction_data.owner)
except Exception:
request.handle_failure(account_balance_response)
frappe.log_error(title=_("Mpesa Account Balance Processing Error"), message=account_balance_response)

View File

@ -31,6 +31,7 @@ class PlaidConnector():
return access_token
def get_link_token(self):
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
token_request = {
"client_name": self.client_name,
"client_id": self.settings.plaid_client_id,
@ -38,7 +39,7 @@ class PlaidConnector():
"products": self.products,
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
"country_codes": country_codes,
"user": {
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType",
"editable_grid": 1,
@ -11,7 +12,8 @@
"plaid_client_id",
"plaid_secret",
"column_break_7",
"plaid_env"
"plaid_env",
"enable_european_access"
],
"fields": [
{
@ -58,10 +60,17 @@
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "enable_european_access",
"fieldtype": "Check",
"label": "Enable European Access"
}
],
"issingle": 1,
"modified": "2020-09-12 02:31:44.542385",
"links": [],
"modified": "2020-10-29 20:24:56.916104",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",

View File

@ -37,7 +37,8 @@
"depends_on": "eval:doc.parenttype==\"Therapy\";",
"fieldname": "counts_completed",
"fieldtype": "Int",
"label": "Counts Completed"
"label": "Counts Completed",
"no_copy": 1
},
{
"fieldname": "assistance_level",
@ -48,7 +49,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-04-10 13:41:06.662351",
"modified": "2020-11-04 18:20:25.583491",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Exercise",

View File

@ -21,6 +21,19 @@ frappe.ui.form.on('Inpatient Medication Entry', {
}
};
});
frm.set_query('warehouse', () => {
return {
filters: {
company: frm.doc.company
}
};
});
},
patient: function(frm) {
if (frm.doc.patient)
frm.set_value('service_unit', '');
},
get_medication_orders: function(frm) {

View File

@ -67,6 +67,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.__islocal",
"fieldname": "filters_section",
"fieldtype": "Section Break",
"label": "Filters"
@ -93,6 +94,7 @@
"options": "Patient"
},
{
"depends_on": "eval:!doc.patient",
"fieldname": "service_unit",
"fieldtype": "Link",
"label": "Healthcare Service Unit",
@ -178,7 +180,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-09-30 23:40:45.528715",
"modified": "2020-11-03 13:22:37.820707",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Medication Entry",

View File

@ -199,6 +199,7 @@ class InpatientMedicationEntry(Document):
def get_pending_medication_orders(entry):
filters, values = get_filters(entry)
to_remove = []
data = frappe.db.sql("""
SELECT
@ -225,7 +226,10 @@ def get_pending_medication_orders(entry):
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if entry.service_unit and doc.service_unit != entry.service_unit:
data.remove(doc)
to_remove.append(doc)
for doc in to_remove:
data.remove(doc)
return data

View File

@ -12,7 +12,8 @@ frappe.ui.form.on('Inpatient Medication Order', {
frm.set_query('patient', () => {
return {
filters: {
'inpatient_record': ['!=', '']
'inpatient_record': ['!=', ''],
'inpatient_status': 'Admitted'
}
};
});

View File

@ -13,43 +13,42 @@ frappe.ui.form.on('Therapy Plan', {
refresh: function(frm) {
if (!frm.doc.__islocal) {
frm.trigger('show_progress_for_therapies');
}
if (!frm.doc.__islocal && frm.doc.status != 'Completed') {
let therapy_types = (frm.doc.therapy_plan_details || []).map(function(d){ return d.therapy_type });
const fields = [{
fieldtype: 'Link',
label: __('Therapy Type'),
fieldname: 'therapy_type',
options: 'Therapy Type',
reqd: 1,
get_query: function() {
return {
filters: { 'therapy_type': ['in', therapy_types]}
if (frm.doc.status != 'Completed') {
let therapy_types = (frm.doc.therapy_plan_details || []).map(function(d){ return d.therapy_type; });
const fields = [{
fieldtype: 'Link',
label: __('Therapy Type'),
fieldname: 'therapy_type',
options: 'Therapy Type',
reqd: 1,
get_query: function() {
return {
filters: { 'therapy_type': ['in', therapy_types]}
};
}
}
}];
}];
frm.add_custom_button(__('Therapy Session'), function() {
frappe.prompt(fields, data => {
frappe.call({
method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_therapy_session',
args: {
therapy_plan: frm.doc.name,
patient: frm.doc.patient,
therapy_type: data.therapy_type,
company: frm.doc.company
},
freeze: true,
callback: function(r) {
if (r.message) {
frappe.model.sync(r.message);
frappe.set_route('Form', r.message.doctype, r.message.name);
frm.add_custom_button(__('Therapy Session'), function() {
frappe.prompt(fields, data => {
frappe.call({
method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_therapy_session',
args: {
therapy_plan: frm.doc.name,
patient: frm.doc.patient,
therapy_type: data.therapy_type,
company: frm.doc.company
},
freeze: true,
callback: function(r) {
if (r.message) {
frappe.model.sync(r.message);
frappe.set_route('Form', r.message.doctype, r.message.name);
}
}
}
});
}, __('Select Therapy Type'), __('Create'));
}, __('Create'));
});
}, __('Select Therapy Type'), __('Create'));
}, __('Create'));
}
if (frm.doc.therapy_plan_template && !frm.doc.invoiced) {
frm.add_custom_button(__('Sales Invoice'), function() {

View File

@ -115,7 +115,8 @@
"fieldname": "therapy_plan_template",
"fieldtype": "Link",
"label": "Therapy Plan Template",
"options": "Therapy Plan Template"
"options": "Therapy Plan Template",
"set_only_once": 1
},
{
"default": "0",
@ -128,7 +129,7 @@
}
],
"links": [],
"modified": "2020-10-23 01:27:42.128855",
"modified": "2020-11-04 18:13:13.564999",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Plan",

View File

@ -30,12 +30,13 @@
"fieldname": "sessions_completed",
"fieldtype": "Int",
"label": "Sessions Completed",
"no_copy": 1,
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-10-08 01:17:34.778028",
"modified": "2020-11-04 18:15:52.173450",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Plan Detail",

View File

@ -22,6 +22,10 @@ frappe.ui.form.on('Therapy Session', {
},
refresh: function(frm) {
if (frm.doc.therapy_plan) {
frm.trigger('filter_therapy_types');
}
if (!frm.doc.__islocal) {
frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]),
@ -36,15 +40,43 @@ frappe.ui.form.on('Therapy Session', {
})
}, 'Create');
frm.add_custom_button(__('Sales Invoice'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session',
frm: frm,
})
}, 'Create');
frappe.db.get_value('Therapy Plan', {'name': frm.doc.therapy_plan}, 'therapy_plan_template', (r) => {
if (r && !r.therapy_plan_template) {
frm.add_custom_button(__('Sales Invoice'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session',
frm: frm,
});
}, 'Create');
}
});
}
},
therapy_plan: function(frm) {
if (frm.doc.therapy_plan) {
frm.trigger('filter_therapy_types');
}
},
filter_therapy_types: function(frm) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Therapy Plan',
name: frm.doc.therapy_plan
},
callback: function(data) {
let therapy_types = (data.message.therapy_plan_details || []).map(function(d){ return d.therapy_type; });
frm.set_query('therapy_type', function() {
return {
filters: { 'therapy_type': ['in', therapy_types]}
};
});
}
});
},
patient: function(frm) {
if (frm.doc.patient) {
frappe.call({
@ -98,19 +130,6 @@ frappe.ui.form.on('Therapy Session', {
frm.set_value(values);
}
});
} else {
let values = {
'patient': '',
'therapy_type': '',
'therapy_plan': '',
'practitioner': '',
'department': '',
'start_date': '',
'start_time': '',
'service_unit': '',
'duration': ''
};
frm.set_value(values);
}
},

View File

@ -194,6 +194,7 @@
"fieldname": "total_counts_completed",
"fieldtype": "Int",
"label": "Total Counts Completed",
"no_copy": 1,
"read_only": 1
},
{
@ -222,7 +223,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-22 23:10:21.178644",
"modified": "2020-11-04 18:14:25.999939",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Session",

View File

@ -56,23 +56,35 @@ frappe.ui.form.on('Production Plan', {
refresh: function(frm) {
if (frm.doc.docstatus === 1) {
frm.trigger("show_progress");
if (frm.doc.status !== "Completed") {
if (frm.doc.po_items && frm.doc.status !== "Closed") {
frm.add_custom_button(__("Work Order"), ()=> {
frm.trigger("make_work_order");
}, __('Create'));
}
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
frm.add_custom_button(__("Material Request"), ()=> {
frm.trigger("make_material_request");
}, __('Create'));
}
if (frm.doc.status === "Closed") {
frm.add_custom_button(__("Re-open"), function() {
frm.events.close_open_production_plan(frm, false);
}, __("Status"));
} else {
frm.add_custom_button(__("Close"), function() {
frm.events.close_open_production_plan(frm, true);
}, __("Status"));
}
}
}
if (frm.doc.docstatus === 1 && frm.doc.po_items
&& frm.doc.status != 'Completed') {
frm.add_custom_button(__("Work Order"), ()=> {
frm.trigger("make_work_order");
}, __('Create'));
if (frm.doc.status !== "Closed") {
frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if (frm.doc.docstatus === 1 && frm.doc.mr_items
&& !in_list(['Material Requested', 'Completed'], frm.doc.status)) {
frm.add_custom_button(__("Material Request"), ()=> {
frm.trigger("make_material_request");
}, __('Create'));
}
frm.page.set_inner_btn_group_as_primary(__('Create'));
frm.trigger("material_requirement");
const projected_qty_formula = ` <table class="table table-bordered" style="background-color: #f9f9f9;">
@ -121,6 +133,18 @@ frappe.ui.form.on('Production Plan', {
set_field_options("projected_qty_formula", projected_qty_formula);
},
close_open_production_plan: (frm, close=false) => {
frappe.call({
method: "set_status",
freeze: true,
doc: frm.doc,
args: {close : close},
callback: function() {
frm.reload_doc();
}
});
},
make_work_order: function(frm) {
frappe.call({
method: "make_work_order",

View File

@ -275,7 +275,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled\nMaterial Requested",
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nClosed\nCancelled\nMaterial Requested",
"print_hide": 1,
"read_only": 1
},
@ -304,9 +304,10 @@
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-02-03 00:25:25.934202",
"modified": "2020-10-26 13:00:54.335319",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@ -219,13 +219,17 @@ class ProductionPlan(Document):
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
frappe.delete_doc('Work Order', d.name)
def set_status(self):
def set_status(self, close=None):
self.status = {
0: 'Draft',
1: 'Submitted',
2: 'Cancelled'
}.get(self.docstatus)
if close:
self.db_set('status', 'Closed')
return
if self.total_produced_qty > 0:
self.status = "In Process"
if self.total_produced_qty == self.total_planned_qty:
@ -235,6 +239,9 @@ class ProductionPlan(Document):
self.update_ordered_status()
self.update_requested_status()
if close is not None:
self.db_set('status', self.status)
def update_ordered_status(self):
update_status = False
for d in self.po_items:
@ -735,10 +742,12 @@ def get_items_for_material_requests(doc, warehouses=None):
mr_items = new_mr_items
if not mr_items:
frappe.msgprint(_("""As raw materials projected quantity is more than required quantity,
there is no need to create material request for the warehouse {0}.
Still if you want to make material request,
kindly enable <b>Ignore Existing Projected Quantity</b> checkbox""").format(doc.get('for_warehouse')))
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
warehouse = frappe.bold(doc.get('for_warehouse'))
message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
message += _(" If you still want to proceed, please enable {0}.").format(to_enable)
frappe.msgprint(message, title=_("Note"))
return mr_items

View File

@ -1,6 +1,6 @@
frappe.listview_settings['Production Plan'] = {
add_fields: ["status"],
filters: [["status", "!=", "Stopped"]],
filters: [["status", "!=", "Closed"]],
get_indicator: function(doc) {
if(doc.status==="Submitted") {
return [__("Not Started"), "orange", "status,=,Submitted"];
@ -10,7 +10,8 @@ frappe.listview_settings['Production Plan'] = {
"In Process": "orange",
"Completed": "green",
"Material Requested": "darkgrey",
"Cancelled": "darkgrey"
"Cancelled": "darkgrey",
"Closed": "grey"
}[doc.status], "status,=," + doc.status];
}
}

View File

@ -11,30 +11,20 @@
{
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-02-02 10:37:16.650836",
"modified": "2020-10-26 12:55:00.778201",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Material Request Warehouse",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",

View File

@ -2,7 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on('Routing', {
setup: function(frm) {
refresh: function(frm) {
frm.trigger("display_sequence_id_column");
},
onload: function(frm) {
frm.trigger("display_sequence_id_column");
},
display_sequence_id_column: function(frm) {
frappe.meta.get_docfield("BOM Operation", "sequence_id",
frm.doc.name).in_list_view = true;

View File

@ -732,3 +732,5 @@ erpnext.patches.v13_0.set_youtube_video_id
erpnext.patches.v13_0.print_uom_after_quantity_patch
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
execute:frappe.delete_doc("Report", "Quoted Item Comparison")

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
for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
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):

View File

@ -0,0 +1,15 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("hr", "doctype", "employee")
if frappe.db.has_column("Employee", "reason_for_resignation"):
frappe.db.sql(""" UPDATE `tabEmployee`
SET reason_for_leaving = reason_for_resignation
WHERE status = 'Left' and reason_for_leaving is null and reason_for_resignation is not null
""")

View File

@ -352,9 +352,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let show_description = function(idx, exist = null) {
if (exist) {
scan_barcode_field.set_new_description(__('Row #{0}: Qty increased by 1', [idx]));
frappe.show_alert({
message: __('Row #{0}: Qty increased by 1', [idx]),
indicator: 'green'
});
} else {
scan_barcode_field.set_new_description(__('Row #{0}: Item added', [idx]));
frappe.show_alert({
message: __('Row #{0}: Item added', [idx]),
indicator: 'green'
});
}
}
@ -365,7 +371,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}).then(r => {
const data = r && r.message;
if (!data || Object.keys(data).length === 0) {
scan_barcode_field.set_new_description(__('Cannot find Item with this barcode'));
frappe.show_alert({
message: __('Cannot find Item with this Barcode'),
indicator: 'red'
});
return;
}
@ -651,7 +660,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let child = frappe.model.add_child(me.frm.doc, "taxes");
child.charge_type = "On Net Total";
child.account_head = tax;
child.rate = rate;
child.rate = 0;
}
});
}

View File

@ -18,7 +18,7 @@
{
"hidden": 0,
"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",
@ -29,11 +29,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"icon": "",
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Quality",
"modified": "2020-04-01 11:28:51.095012",
"modified": "2020-10-27 16:28:54.138055",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality",
@ -47,6 +47,7 @@
"type": "DocType"
},
{
"doc_view": "Tree",
"label": "Quality Procedure",
"link_to": "Quality Procedure",
"type": "DocType"
@ -55,6 +56,33 @@
"label": "Quality Inspection",
"link_to": "Quality Inspection",
"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
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",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"corrective_preventive",
"document_type",
"goal",
"review",
"feedback",
"status",
"cb_00",
"date",
"document_name",
"goal",
"procedure",
"status",
"sb_00",
"resolutions"
],
"fields": [
{
"depends_on": "eval:doc.type == 'Quality Review'",
"fetch_from": "review.goal",
"fieldname": "goal",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Goal",
"options": "Quality Goal",
"read_only": 1
"options": "Quality Goal"
},
{
"default": "Today",
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
@ -34,34 +36,20 @@
"read_only": 1
},
{
"depends_on": "eval:doc.type == 'Quality Review'",
"fieldname": "procedure",
"fieldtype": "Link",
"label": "Procedure",
"options": "Quality Procedure",
"read_only": 1
"options": "Quality Procedure"
},
{
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Open\nClosed"
},
{
"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
"options": "Open\nCompleted",
"read_only": 1
},
{
"default": "Corrective",
@ -86,9 +74,24 @@
"fieldtype": "Table",
"label": "Resolutions",
"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",
"module": "Quality Management",
"name": "Quality Action",

View File

@ -7,4 +7,5 @@ import frappe
from frappe.model.document import 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 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):
def test_quality_action(self):
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})
# quality action has no code
pass

View File

@ -1,33 +1,54 @@
{
"actions": [],
"creation": "2019-05-26 20:36:44.337186",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"problem",
"sb_00",
"resolution"
"resolution",
"status",
"responsible",
"completion_by"
],
"fields": [
{
"fieldname": "problem",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Review"
},
{
"fieldname": "sb_00",
"fieldtype": "Section Break"
"label": "Problem"
},
{
"fieldname": "resolution",
"fieldtype": "Text Editor",
"in_list_view": 1,
"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,
"modified": "2019-05-28 13:09:50.435323",
"links": [],
"modified": "2020-10-21 12:59:25.566682",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Action Resolution",

View File

@ -2,31 +2,9 @@
// For license information, please see license.txt
frappe.ui.form.on('Quality Feedback', {
refresh: function(frm) {
frm.set_value("date", frappe.datetime.get_today());
},
template: function(frm) {
if (frm.doc.template) {
frappe.call({
"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();
}
}
});
frm.call('set_parameters');
}
}
});

View File

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

View File

@ -7,4 +7,17 @@ import frappe
from frappe.model.document import 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 unittest
from erpnext.quality_management.doctype.quality_feedback_template.test_quality_feedback_template import create_template
class TestQualityFeedback(unittest.TestCase):
def test_quality_feedback(self):
create_template()
test_create_feedback = create_feedback()
test_get_feedback = get_feedback()
template = frappe.get_doc(dict(
doctype = 'Quality Feedback Template',
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():
create_customer()
self.assertEqual(template.parameters[0].parameter, feedback.parameters[0].parameter)
feedabck = frappe.get_doc({
"doctype": "Quality Feedback",
"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)
feedback.delete()
template.delete()

View File

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

View File

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

View File

@ -7,31 +7,4 @@ import frappe
import unittest
class TestQualityFeedbackTemplate(unittest.TestCase):
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"})
pass

View File

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

View File

@ -1,5 +1,6 @@
{
"autoname": "format:GOAL-{goal}",
"actions": [],
"autoname": "field:goal",
"creation": "2018-10-02 12:17:41.727541",
"doctype": "DocType",
"editable_grid": 1,
@ -7,27 +8,14 @@
"field_order": [
"goal",
"frequency",
"created_by",
"cb_00",
"procedure",
"weekday",
"quarter",
"date",
"sb_00",
"revision",
"cb_01",
"revised_on",
"sb_01",
"objectives"
],
"fields": [
{
"fieldname": "created_by",
"fieldtype": "Link",
"label": "Created By",
"options": "User",
"read_only": 1
},
{
"default": "None",
"fieldname": "frequency",
@ -50,20 +38,6 @@
"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"
},
{
"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';",
"fieldname": "weekday",
@ -75,15 +49,6 @@
"fieldname": "cb_00",
"fieldtype": "Column Break"
},
{
"fieldname": "sb_00",
"fieldtype": "Section Break",
"label": "Revision and Revised On"
},
{
"fieldname": "cb_01",
"fieldtype": "Column Break"
},
{
"fieldname": "sb_01",
"fieldtype": "Section Break",
@ -101,18 +66,17 @@
"label": "Goal",
"reqd": 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",
"module": "Quality Management",
"name": "Quality Goal",

View File

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

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
class TestQualityGoal(unittest.TestCase):
def test_quality_goal(self):
create_procedure()
create_unit()
test_create_goal = create_goal()
test_get_goal = get_goal()
# no code, just a basic sanity check
goal = get_quality_goal()
self.assertTrue(goal)
goal.delete()
self.assertEquals(test_create_goal, test_get_goal)
def create_goal():
goal = frappe.get_doc({
"doctype": "Quality Goal",
"goal": "_Test Quality Goal",
"procedure": "PRC-_Test Quality Procedure",
"objectives": [
{
"objective": "_Test Quality Objective",
"target": "4",
"uom": "_Test UOM"
}
def get_quality_goal():
return frappe.get_doc(dict(
doctype = 'Quality Goal',
goal = 'Test Quality Module',
frequency = 'Daily',
objectives = [
dict(objective = 'Check test cases', target='100', uom='Percent')
]
})
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()
)).insert()

View File

@ -2,8 +2,5 @@
// For license information, please see license.txt
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": [],
"autoname": "naming_series:",
"autoname": "format:QA-MEET-{YY}-{MM}-{DD}",
"creation": "2018-10-15 16:25:41.548432",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"date",
"cb_00",
"status",
"cb_00",
"sb_00",
"agenda",
"sb_01",
"minutes"
],
"fields": [
{
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"read_only": 1
},
{
"default": "Open",
"fieldname": "status",
@ -55,16 +46,11 @@
"fieldname": "sb_01",
"fieldtype": "Section Break",
"label": "Minutes"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "MTNG-.YYYY.-.MM.-.DD.-"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-05-19 13:18:59.821740",
"modified": "2020-10-27 16:36:45.657883",
"modified_by": "Administrator",
"module": "Quality Management",
"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 unittest
from erpnext.quality_management.doctype.quality_review.test_quality_review import create_review
class TestQualityMeeting(unittest.TestCase):
def test_quality_meeting(self):
create_review()
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
# nothing to test
pass

View File

@ -1,19 +1,22 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:PRC-{quality_procedure_name}",
"autoname": "field:quality_procedure_name",
"creation": "2018-10-06 00:06:29.756804",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"quality_procedure_name",
"process_owner",
"process_owner_full_name",
"section_break_3",
"processes",
"sb_00",
"parent_quality_procedure",
"is_group",
"sb_00",
"processes",
"lft",
"rgt",
"lft",
"old_parent"
],
"fields": [
@ -34,14 +37,14 @@
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
"label": "Lft",
"label": "Left Index",
"read_only": 1
},
{
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
"label": "Rgt",
"label": "Right Index",
"read_only": 1
},
{
@ -54,7 +57,7 @@
{
"fieldname": "sb_00",
"fieldtype": "Section Break",
"label": "Processes"
"label": "Parent"
},
{
"fieldname": "processes",
@ -67,12 +70,52 @@
"fieldtype": "Data",
"in_list_view": 1,
"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,
"links": [],
"modified": "2020-10-13 11:46:07.744194",
"links": [
{
"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",
"module": "Quality Management",
"name": "Quality Procedure",

View File

@ -14,69 +14,58 @@ class QualityProcedure(NestedSet):
self.check_for_incorrect_child()
def on_update(self):
NestedSet.on_update(self)
self.set_parent()
def after_insert(self):
self.set_parent()
#if Child is Added through Tree View.
# add child to parent if missing
if self.parent_quality_procedure:
parent_quality_procedure = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
parent_quality_procedure.append("processes", {"procedure": self.name})
parent_quality_procedure.save()
parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
if not [d for d in parent.processes if d.procedure == self.name]:
parent.append("processes", {"procedure": self.name, "process_description": self.name})
parent.save()
def on_trash(self):
if self.parent_quality_procedure:
doc = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
for process in doc.processes:
if process.procedure == self.name:
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)
# clear from child table (sub procedures)
frappe.db.sql('''update `tabQuality Procedure Process`
set `procedure`='' where `procedure`=%s''', self.name)
NestedSet.on_trash(self, allow_root_deletion=True)
def set_parent(self):
rebuild_tree('Quality Procedure', 'parent_quality_procedure')
for process in self.processes:
# 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")
if not parent_quality_procedure and process.procedure:
has_parent = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
if not has_parent and process.procedure:
frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
def check_for_incorrect_child(self):
for process in self.processes:
if process.procedure:
self.is_group = 1
# Check if any child process belongs to another parent.
parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
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"))
self.is_group = 1
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
if parent is None or parent == "All Quality Procedures":
parent = ""
return frappe.db.sql("""
select
name as value,
is_group as expandable
from
`tab{doctype}`
where
ifnull(parent_quality_procedure, "")={parent}
""".format(
doctype = doctype,
parent=frappe.db.escape(parent)
), as_dict=1)
if parent:
parent_procedure = frappe.get_doc('Quality Procedure', parent)
# return the list in order
return [dict(
value=d.procedure,
expandable=frappe.db.get_value('Quality Procedure', d.procedure, 'is_group'))
for d in parent_procedure.processes if d.procedure
]
else:
return frappe.get_all(doctype, fields=['name as value', 'is_group as expandable'],
filters = dict(parent_quality_procedure = parent), order_by='name asc')
@frappe.whitelist()
def add_node():
@ -88,4 +77,4 @@ def add_node():
if args.parent_quality_procedure == 'All Quality Procedures':
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,
root_label: "All Quality Procedures",
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 unittest
class TestQualityProcedure(unittest.TestCase):
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()
from .quality_procedure import add_node
self.assertEquals(test_create_procedure, test_get_procedure.get("name"))
self.assertEquals(test_create_nested_procedure, test_get_nested_procedure.get("name"))
class TestQualityProcedure(unittest.TestCase):
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():
procedure = frappe.get_doc({
"doctype": "Quality Procedure",
"quality_procedure_name": "_Test Quality Procedure",
"processes": [
{
"process_description": "_Test Quality Procedure Table",
}
return frappe.get_doc(dict(
doctype = 'Quality Procedure',
quality_procedure_name = 'Test Procedure 1',
is_group = 1,
processes = [
dict(process_description = 'Test Step 1')
]
})
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}
)).insert()

View File

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

View File

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

View File

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

View File

@ -7,7 +7,26 @@ import frappe
from frappe.model.document import 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():
day = frappe.utils.getdate().day
@ -24,7 +43,7 @@ def review():
elif goal.frequency == 'Monthly' and goal.date == str(day):
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)
def create_review(goal):
@ -36,15 +55,6 @@ def create_review(goal):
"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)
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 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 ..quality_goal.test_quality_goal import get_quality_goal
from .quality_review import review
class TestQualityReview(unittest.TestCase):
def test_review_creation(self):
quality_goal = get_quality_goal()
review()
def test_quality_review(self):
create_procedure()
create_unit()
create_goal()
test_create_review = create_review()
test_get_review = get_review()
self.assertEquals(test_create_review, test_get_review)
# check if review exists
quality_review = frappe.get_doc('Quality Review', dict(goal = quality_goal.name))
self.assertEqual(quality_goal.objectives[0].target, quality_review.reviews[0].target)
quality_review.delete()
def create_review():
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
quality_goal.delete()

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2019-05-26 15:17:44.796958",
"doctype": "DocType",
"editable_grid": 1,
@ -9,10 +10,12 @@
"target",
"uom",
"sb_00",
"status",
"review"
],
"fields": [
{
"columns": 3,
"fieldname": "objective",
"fieldtype": "Text",
"in_list_view": 1,
@ -20,6 +23,7 @@
"read_only": 1
},
{
"columns": 2,
"fieldname": "target",
"fieldtype": "Data",
"in_list_view": 1,
@ -27,6 +31,7 @@
"read_only": 1
},
{
"columns": 1,
"fetch_from": "target_unit",
"fieldname": "uom",
"fieldtype": "Link",
@ -49,10 +54,20 @@
{
"fieldname": "cb_00",
"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,
"modified": "2019-05-26 16:14:12.586128",
"links": [],
"modified": "2020-10-27 16:28:20.908637",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Review Objective",

View File

@ -139,7 +139,7 @@ def get_place_of_supply(party_details, doctype):
if not frappe.get_meta('Address').has_field('gst_state'): return
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
address_name = party_details.shipping_address_name or party_details.customer_address
address_name = party_details.customer_address or party_details.shipping_address_name
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
address_name = party_details.shipping_address or party_details.supplier_address

View File

@ -14,11 +14,9 @@ from six import string_types
def get_items(start, page_length, price_list, item_group, pos_profile, search_value=""):
data = dict()
result = []
warehouse, show_only_available_items = "", False
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
if not allow_negative_stock:
warehouse, show_only_available_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'show_only_available_items'])
warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items'])
if not frappe.db.exists('Item Group', item_group):
item_group = get_root_of('Item Group')
@ -48,7 +46,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
bin_join_selection, bin_join_condition = "", ""
if show_only_available_items:
if hide_unavailable_items:
bin_join_selection = ", `tabBin` bin"
bin_join_condition = "AND bin.warehouse = %(warehouse)s AND bin.item_code = item.name AND bin.actual_qty > 0"
@ -97,7 +95,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
for item in items_data:
item_code = item.item_code
item_price = item_prices.get(item_code) or {}
if not allow_negative_stock:
if allow_negative_stock:
item_stock_qty = frappe.db.sql("""select ifnull(sum(actual_qty), 0) from `tabBin` where item_code = %s""", item_code)[0][0]
else:
item_stock_qty = get_stock_availability(item_code, warehouse)
@ -231,13 +229,31 @@ def set_customer_info(fieldname, customer, value=""):
frappe.db.set_value('Customer', customer, 'loyalty_program', value)
contact = frappe.get_cached_value('Customer', customer, 'customer_primary_contact')
if not contact:
contact = frappe.db.sql("""
SELECT parent FROM `tabDynamic Link`
WHERE
parenttype = 'Contact' AND
parentfield = 'links' AND
link_doctype = 'Customer' AND
link_name = %s
""", (customer), as_dict=1)
contact = contact[0].get('parent') if contact else None
if contact:
contact_doc = frappe.get_doc('Contact', contact)
if fieldname == 'email_id':
contact_doc.set('email_ids', [{ 'email_id': value, 'is_primary': 1}])
frappe.db.set_value('Customer', customer, 'email_id', value)
elif fieldname == 'mobile_no':
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
frappe.db.set_value('Customer', customer, 'mobile_no', value)
contact_doc.save()
if not contact:
new_contact = frappe.new_doc('Contact')
new_contact.is_primary_contact = 1
new_contact.first_name = customer
new_contact.set('links', [{'link_doctype': 'Customer', 'link_name': customer}])
new_contact.save()
contact = new_contact.name
frappe.db.set_value('Customer', customer, 'customer_primary_contact', contact)
contact_doc = frappe.get_doc('Contact', contact)
if fieldname == 'email_id':
contact_doc.set('email_ids', [{ 'email_id': value, 'is_primary': 1}])
frappe.db.set_value('Customer', customer, 'email_id', value)
elif fieldname == 'mobile_no':
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
frappe.db.set_value('Customer', customer, 'mobile_no', value)
contact_doc.save()

View File

@ -637,7 +637,7 @@ erpnext.PointOfSale.Controller = class {
if (!(available_qty > 0)) {
frappe.model.clear_doc(item_row.doctype, item_row.name);
frappe.throw({
title: _("Not Available"),
title: __("Not Available"),
message: __('Item Code: {0} is not available under warehouse {1}.', [bold_item_code, bold_warehouse])
})
} else if (available_qty < qty_needed) {

View File

@ -81,7 +81,7 @@ erpnext.PointOfSale.ItemSelector = class {
function get_item_image_html() {
if (item_image) {
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${item_image}" style="object-fit: cover;">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>`
} else {
return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">

View File

@ -409,7 +409,7 @@ erpnext.PointOfSale.Payment = class {
${
shortcuts.map(s => {
return `<div class="shortcut rounded bg-light-grey text-dark-grey pt-2 pb-2 no-select pointer" data-value="${s}">
${format_currency(s, currency)}
${format_currency(s, currency, 0)}
</div>`
}).join('')
}

View File

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

View File

@ -615,6 +615,15 @@ class StockEntry(StockController):
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)))
elif not row.po_detail:
filters = {
"parent": self.purchase_order, "docstatus": 1,
"rm_item_code": row.item_code, "main_item_code": row.subcontracted_item
}
po_detail = frappe.db.get_value("Purchase Order Item Supplied", filters, "name")
if po_detail:
row.db_set("po_detail", po_detail)
def validate_bom(self):
for d in self.get('items'):

View File

@ -168,6 +168,7 @@ def get_stock_ledger_entries(filters, items):
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
and is_cancelled = 0
order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
(item_conditions_sql, conditions), as_dict=1)

View File

@ -288,7 +288,6 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
return
convertible_cols = {}
is_dict_obj = False
if isinstance(result[0], dict):
is_dict_obj = True
@ -310,13 +309,13 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
for row_idx, row in enumerate(result):
data = row.items() if is_dict_obj else enumerate(row)
for key, value in data:
if not key in convertible_columns or not conversion_factors[row_idx]:
if key not in convertible_columns or not conversion_factors[row_idx-1]:
continue
if convertible_columns.get(key) == 'rate':
new_value = flt(value) * conversion_factors[row_idx]
new_value = flt(value) * conversion_factors[row_idx-1]
else:
new_value = flt(value) / conversion_factors[row_idx]
new_value = flt(value) / conversion_factors[row_idx-1]
if not is_dict_obj:
row.insert(key+1, new_value)

View File

@ -7,7 +7,7 @@ import json
from frappe import _
from frappe import utils
from frappe.model.document import Document
from frappe.utils import time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime, time_diff_in_seconds, time_diff
from frappe.utils import now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
from datetime import datetime, timedelta
from frappe.model.mapper import get_mapped_doc
from frappe.utils.user import is_website_user
@ -355,13 +355,13 @@ def set_service_level_agreement_variance(issue=None):
doc = frappe.get_doc("Issue", issue.name)
if not doc.first_responded_on: # first_responded_on set when first reply is sent to customer
variance = round(time_diff_in_hours(doc.response_by, current_time), 2)
variance = round(time_diff_in_seconds(doc.response_by, current_time), 2)
frappe.db.set_value(dt="Issue", dn=doc.name, field="response_by_variance", val=variance, update_modified=False)
if variance < 0:
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
if not doc.resolution_date: # resolution_date set when issue has been closed
variance = round(time_diff_in_hours(doc.resolution_by, current_time), 2)
variance = round(time_diff_in_seconds(doc.resolution_by, current_time), 2)
frappe.db.set_value(dt="Issue", dn=doc.name, field="resolution_by_variance", val=variance, update_modified=False)
if variance < 0:
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)

View File

@ -473,7 +473,6 @@ Cannot deduct when category is for 'Valuation' or 'Valuation and Total',Kan nie
Cannot deduct when category is for 'Valuation' or 'Vaulation and Total',Kan nie aftrek as die kategorie vir &#39;Waardasie&#39; of &#39;Vaulering en Totaal&#39; is nie.,
"Cannot delete Serial No {0}, as it is used in stock transactions","Kan nie reeksnommer {0} uitvee nie, aangesien dit in voorraadtransaksies gebruik word",
Cannot enroll more than {0} students for this student group.,Kan nie meer as {0} studente vir hierdie studente groep inskryf nie.,
Cannot find Item with this barcode,Kan geen item met hierdie strepieskode vind nie,
Cannot find active Leave Period,Kan nie aktiewe verlofperiode vind nie,
Cannot produce more Item {0} than Sales Order quantity {1},Kan nie meer item {0} produseer as hoeveelheid van die bestelling {1},
Cannot promote Employee with status Left,Kan nie werknemer bevorder met status links nie,
@ -690,7 +689,6 @@ Create Variants,Skep variante,
"Create and manage daily, weekly and monthly email digests.","Skep en bestuur daaglikse, weeklikse en maandelikse e-posverdelings.",
Create customer quotes,Skep kliënte kwotasies,
Create rules to restrict transactions based on values.,Skep reëls om transaksies gebaseer op waardes te beperk.,
Created By,Gemaak deur,
Created {0} scorecards for {1} between: ,Geskep {0} telkaarte vir {1} tussen:,
Creating Company and Importing Chart of Accounts,Skep &#39;n maatskappy en voer rekeningrekeninge in,
Creating Fees,Fooie skep,
@ -1078,7 +1076,6 @@ For Warehouse is required before Submit,Vir die pakhuis word vereis voor indieni
For row {0}: Enter Planned Qty,Vir ry {0}: Gee beplande hoeveelheid,
"For {0}, only credit accounts can be linked against another debit entry",Vir {0} kan slegs kredietrekeninge gekoppel word teen &#39;n ander debietinskrywing,
"For {0}, only debit accounts can be linked against another credit entry",Vir {0} kan slegs debietrekeninge gekoppel word teen &#39;n ander kredietinskrywing,
Form View,Form View,
Forum Activity,Forum Aktiwiteit,
Free item code is not selected,Gratis itemkode word nie gekies nie,
Freight and Forwarding Charges,Vrag en vragkoste,
@ -2638,7 +2635,6 @@ Send SMS,Stuur SMS,
Send mass SMS to your contacts,Stuur massa-SMS na jou kontakte,
Sensitivity,sensitiwiteit,
Sent,gestuur,
Serial #,Serie #,
Serial No and Batch,Serial No and Batch,
Serial No is mandatory for Item {0},Volgnummer is verpligtend vir item {0},
Serial No {0} does not belong to Batch {1},Reeksnommer {0} hoort nie by bondel {1},
@ -3303,7 +3299,6 @@ Welcome to ERPNext,Welkom by ERPNext,
What do you need help with?,Waarmee het jy hulp nodig?,
What does it do?,Wat doen dit?,
Where manufacturing operations are carried.,Waar vervaardigingsbedrywighede uitgevoer word.,
"While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA","Terwyl u rekening vir kindermaatskappy {0} skep, word ouerrekening {1} nie gevind nie. Skep asseblief die ouerrekening in die ooreenstemmende COA",
White,wit,
Wire Transfer,Elektroniese oorbetaling,
WooCommerce Products,WooCommerce Produkte,
@ -3493,6 +3488,7 @@ Likes,Hou,
Merge with existing,Voeg saam met bestaande,
Office,kantoor,
Orientation,geaardheid,
Parent,Ouer,
Passive,passiewe,
Payment Failed,Betaling misluk,
Percent,persent,
@ -3543,6 +3539,7 @@ Shift,verskuiwing,
Show {0},Wys {0},
"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Spesiale karakters behalwe &quot;-&quot;, &quot;#&quot;, &quot;.&quot;, &quot;/&quot;, &quot;{&quot; En &quot;}&quot; word nie toegelaat in die naamreekse nie",
Target Details,Teikenbesonderhede,
{0} already has a Parent Procedure {1}.,{0} het reeds &#39;n ouerprosedure {1}.,
API,API,
Annual,jaarlikse,
Approved,goedgekeur,
@ -7558,10 +7555,6 @@ Quality Feedback Template Parameter,Parameter vir gehalte-terugvoersjabloon,
Quality Goal,Kwaliteit doel,
Monitoring Frequency,Monitor frekwensie,
Weekday,weekdag,
January-April-July-October,Januarie-April-Julie-Oktober,
Revision and Revised On,Hersiening en hersien op,
Revision,hersiening,
Revised On,Hersien op,
Objectives,doelwitte,
Quality Goal Objective,Kwaliteit Doelwit,
Objective,Doel,
@ -7574,7 +7567,6 @@ Parent Procedure,Ouerprosedure,
Processes,prosesse,
Quality Procedure Process,Kwaliteit prosedure proses,
Process Description,Prosesbeskrywing,
Child Procedure,Kinderprosedure,
Link existing Quality Procedure.,Koppel die bestaande kwaliteitsprosedure.,
Additional Information,Bykomende inligting,
Quality Review Objective,Doel van gehaltehersiening,
@ -9091,7 +9083,6 @@ Unmarked days,Ongemerkte dae,
Absent Days,Afwesige dae,
Conditions and Formula variable and example,Voorwaardes en formule veranderlike en voorbeeld,
Feedback By,Terugvoer deur,
MTNG-.YYYY.-.MM.-.DD.-,MTNG-.YYYY .-. MM .-. DD.-,
Manufacturing Section,Vervaardigingsafdeling,
"By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ",Die kliëntnaam word standaard ingestel volgens die volledige naam wat ingevoer is. As u wil hê dat klante deur &#39;n,
Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.,Stel die standaardpryslys op wanneer u &#39;n nuwe verkoopstransaksie skep. Itempryse word uit hierdie pryslys gehaal.,
@ -9692,7 +9683,6 @@ Available Balance,Beskikbare balans,
Reserved Balance,Gereserveerde balans,
Uncleared Balance,Onduidelike balans,
Payment related to {0} is not completed,Betaling wat verband hou met {0} is nie voltooi nie,
Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. Please select valid serial no.,Ry # {}: reeksnommer {}. {} is reeds oorgedra na &#39;n ander POS-faktuur. Kies &#39;n geldige reeksnr.,
Row #{}: Item Code: {} is not available under warehouse {}.,Ry # {}: Itemkode: {} is nie beskikbaar onder pakhuis {} nie.,
Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.,Ry # {}: voorraadhoeveelheid nie genoeg vir artikelkode: {} onder pakhuis {}. Beskikbare hoeveelheid {}.,
Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.,Ry # {}: kies &#39;n reeksnommer en &#39;n bondel teenoor item: {} of verwyder dit om die transaksie te voltooi.,
@ -9732,3 +9722,115 @@ Quantity not available for {0} in warehouse {1},Hoeveelheid nie beskikbaar vir {
Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.,Aktiveer asseblief Laat negatiewe voorraad toe in voorraadinstellings of skep voorraadinskrywing om voort te gaan.,
No Inpatient Record found against patient {0},Geen pasiëntrekord gevind teen pasiënt nie {0},
An Inpatient Medication Order {0} against Patient Encounter {1} already exists.,Daar bestaan reeds &#39;n medikasiebevel vir binnepasiënte {0} teen pasiëntontmoeting {1}.,
Allow In Returns,Laat opbrengste toe,
Hide Unavailable Items,Versteek nie-beskikbare items,
Apply Discount on Discounted Rate,Pas afslag toe op afslag,
Therapy Plan Template,Terapieplan-sjabloon,
Fetching Template Details,Haal sjabloonbesonderhede op,
Linked Item Details,Gekoppelde itembesonderhede,
Therapy Types,Terapie tipes,
Therapy Plan Template Detail,Terapieplan sjabloonbesonderhede,
Non Conformance,Nie-ooreenstemming,
Process Owner,Proses eienaar,
Corrective Action,Korrektiewe aksie,
Preventive Action,Voorkomende aksie,
Problem,Probleem,
Responsible,Verantwoordelik,
Completion By,Voltooiing deur,
Process Owner Full Name,Proses eienaar se volle naam,
Right Index,Regte indeks,
Left Index,Linkse indeks,
Sub Procedure,Subprosedure,
Passed,Geslaag,
Print Receipt,Drukbewys,
Edit Receipt,Wysig kwitansie,
Focus on search input,Fokus op soekinsette,
Focus on Item Group filter,Fokus op Item Group filter,
Checkout Order / Submit Order / New Order,Afhandeling Bestelling / Dien Bestelling / Nuwe Bestelling in,
Add Order Discount,Bestel afslag byvoeg,
Item Code: {0} is not available under warehouse {1}.,Itemkode: {0} is nie beskikbaar onder pakhuis {1} nie.,
Serial numbers unavailable for Item {0} under warehouse {1}. Please try changing warehouse.,Reeksnommers nie beskikbaar vir item {0} onder pakhuis {1} nie. Probeer om die pakhuis te verander.,
Fetched only {0} available serial numbers.,Slegs {0} beskikbare reeksnommers gekry.,
Switch Between Payment Modes,Skakel tussen betaalmetodes,
Enter {0} amount.,Voer {0} bedrag in.,
You don't have enough points to redeem.,U het nie genoeg punte om af te los nie.,
You can redeem upto {0}.,U kan tot {0} gebruik.,
Enter amount to be redeemed.,Voer die bedrag in wat afgelos moet word.,
You cannot redeem more than {0}.,U kan nie meer as {0} gebruik nie.,
Open Form View,Maak vormaansig oop,
POS invoice {0} created succesfully,POS-faktuur {0} suksesvol geskep,
Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.,Voorraadhoeveelheid nie genoeg vir artikelkode: {0} onder pakhuis {1}. Beskikbare hoeveelheid {2}.,
Serial No: {0} has already been transacted into another POS Invoice.,Serienommer: {0} is reeds oorgedra na &#39;n ander POS-faktuur.,
Balance Serial No,Saldo Reeksnr,
Warehouse: {0} does not belong to {1},Pakhuis: {0} behoort nie tot {1},
Please select batches for batched item {0},Kies groepe vir &#39;n partytjie-item {0},
Please select quantity on row {0},Kies hoeveelheid in ry {0},
Please enter serial numbers for serialized item {0},Voer die reeksnommers in vir die reeks-item {0},
Batch {0} already selected.,Bondel {0} reeds gekies.,
Please select a warehouse to get available quantities,Kies &#39;n pakhuis om beskikbare hoeveelhede te kry,
"For transfer from source, selected quantity cannot be greater than available quantity",Vir oordrag vanaf die bron kan die gekose hoeveelheid nie groter wees as die beskikbare hoeveelheid nie,
Cannot find Item with this Barcode,Kan nie item met hierdie strepieskode vind nie,
{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2},{0} is verpligtend. Miskien word valuta-rekord nie vir {1} tot {2} geskep nie,
{} has submitted assets linked to it. You need to cancel the assets to create purchase return.,"{} het bates wat daaraan gekoppel is, ingedien. U moet die bates kanselleer om die aankoopopbrengs te skep.",
Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.,"Kan nie hierdie dokument kanselleer nie, want dit is gekoppel aan die ingediende bate {0}. Kanselleer dit asseblief om voort te gaan.",
Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.,Ry # {}: Reeksnr. {} Is reeds oorgedra na &#39;n ander POS-faktuur. Kies &#39;n geldige reeksnr.,
Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.,Ry # {}: reeksnommers. {} Is reeds in &#39;n ander POS-faktuur oorgedra. Kies &#39;n geldige reeksnr.,
Item Unavailable,Item nie beskikbaar nie,
Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {},"Ry # {}: reeksnommer {} kan nie teruggestuur word nie, aangesien dit nie op die oorspronklike faktuur gedoen is nie {}",
Please set default Cash or Bank account in Mode of Payment {},Stel die verstek kontant- of bankrekening in die betaalmetode {},
Please set default Cash or Bank account in Mode of Payments {},Stel asseblief die standaard kontant- of bankrekening in die modus van betalings {},
Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.,Maak seker dat die {} rekening &#39;n balansstaatrekening is. U kan die ouerrekening in &#39;n balansrekening verander of &#39;n ander rekening kies.,
Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.,Maak seker dat die {} rekening &#39;n betaalbare rekening is. Verander die rekeningtipe na Betaalbaar of kies &#39;n ander rekening.,
Row {}: Expense Head changed to {} ,Ry {}: Onkostekop verander na {},
because account {} is not linked to warehouse {} ,omdat rekening {} nie aan pakhuis gekoppel is nie {},
or it is not the default inventory account,of dit is nie die standaardvoorraadrekening nie,
Expense Head Changed,Uitgawehoof verander,
because expense is booked against this account in Purchase Receipt {},omdat die onkoste teen hierdie rekening in die aankoopbewys {} bespreek word,
as no Purchase Receipt is created against Item {}. ,aangesien geen aankoopbewys teen item {} geskep word nie.,
This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice,Dit word gedoen om rekeningkunde te hanteer vir gevalle waar aankoopbewys na aankoopfaktuur geskep word,
Purchase Order Required for item {},Bestelling benodig vir item {},
To submit the invoice without purchase order please set {} ,Stel die {} in om die faktuur sonder &#39;n bestelling in te dien,
as {} in {},soos in {},
Mandatory Purchase Order,Verpligte bestelling,
Purchase Receipt Required for item {},Aankoopbewys benodig vir item {},
To submit the invoice without purchase receipt please set {} ,Stel die {} in om die faktuur sonder aankoopbewys in te dien.,
Mandatory Purchase Receipt,Verpligte aankoopbewys,
POS Profile {} does not belongs to company {},POS-profiel {} behoort nie tot die maatskappy nie {},
User {} is disabled. Please select valid user/cashier,Gebruiker {} is gedeaktiveer. Kies &#39;n geldige gebruiker / kassier,
Row #{}: Original Invoice {} of return invoice {} is {}. ,Ry # {}: oorspronklike faktuur {} van retourfaktuur {} is {}.,
Original invoice should be consolidated before or along with the return invoice.,Die oorspronklike faktuur moet voor of saam met die retoervaktuur gekonsolideer word.,
You can add original invoice {} manually to proceed.,U kan oorspronklike fakture {} handmatig byvoeg om voort te gaan.,
Please ensure {} account is a Balance Sheet account. ,Maak seker dat die {} rekening &#39;n balansstaatrekening is.,
You can change the parent account to a Balance Sheet account or select a different account.,U kan die ouerrekening in &#39;n balansrekening verander of &#39;n ander rekening kies.,
Please ensure {} account is a Receivable account. ,Maak seker dat die {} rekening &#39;n ontvangbare rekening is.,
Change the account type to Receivable or select a different account.,Verander die rekeningtipe na Ontvangbaar of kies &#39;n ander rekening.,
{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {},{} kan nie gekanselleer word nie omdat die verdienste van die Lojaliteitspunte afgelos is. Kanselleer eers die {} Nee {},
already exists,bestaan alreeds,
POS Closing Entry {} against {} between selected period,POS-sluitingsinskrywing {} teen {} tussen die gekose periode,
POS Invoice is {},POS-faktuur is {},
POS Profile doesn't matches {},POS-profiel stem nie ooreen nie {},
POS Invoice is not {},POS-faktuur is nie {},
POS Invoice isn't created by user {},POS-faktuur word nie deur gebruiker {} geskep nie,
Row #{}: {},Ry # {}: {},
Invalid POS Invoices,Ongeldige POS-fakture,
Please add the account to root level Company - {},Voeg die rekening by die maatskappy se wortelvlak - {},
"While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA","Terwyl u &#39;n rekening vir Child Company {0} skep, word die ouerrekening {1} nie gevind nie. Skep asseblief die ouerrekening in ooreenstemmende COA",
Account Not Found,Rekening nie gevind nie,
"While creating account for Child Company {0}, parent account {1} found as a ledger account.","Terwyl u &#39;n rekening vir Child Company {0} skep, word die ouerrekening {1} as &#39;n grootboekrekening gevind.",
Please convert the parent account in corresponding child company to a group account.,Skakel asseblief die ouerrekening in die ooreenstemmende kindermaatskappy om na &#39;n groeprekening.,
Invalid Parent Account,Ongeldige ouerrekening,
"Renaming it is only allowed via parent company {0}, to avoid mismatch.","Om dit te hernoem, is slegs toegelaat via moedermaatskappy {0}, om wanverhouding te voorkom.",
"If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.","As u {0} {1} hoeveelhede van die artikel {2} het, sal die skema {3} op die item toegepas word.",
"If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.","As u {0} {1} die waarde van item {2} het, sal die skema {3} op die item toegepas word.",
"As the field {0} is enabled, the field {1} is mandatory.","Aangesien die veld {0} geaktiveer is, is die veld {1} verpligtend.",
"As the field {0} is enabled, the value of the field {1} should be more than 1.","Aangesien die veld {0} geaktiveer is, moet die waarde van die veld {1} meer as 1 wees.",
Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2},"Kan nie reeksnommer {0} van die artikel {1} lewer nie, aangesien dit gereserveer is vir die volledige bestelling {2}",
"Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}.",Verkooporder {0} het &#39;n bespreking vir die artikel {1}. U kan slegs gereserveerde {1} teen {0} aflewer.,
{0} Serial No {1} cannot be delivered,{0} Reeksnr. {1} kan nie afgelewer word nie,
Row {0}: Subcontracted Item is mandatory for the raw material {1},Ry {0}: Item uit die onderkontrak is verpligtend vir die grondstof {1},
"As there are sufficient raw materials, Material Request is not required for Warehouse {0}.","Aangesien daar voldoende grondstowwe is, is materiaalversoek nie nodig vir pakhuis {0} nie.",
" If you still want to proceed, please enable {0}.",Skakel {0} aan as u nog steeds wil voortgaan.,
The item referenced by {0} - {1} is already invoiced,Die item waarna verwys word deur {0} - {1} word reeds gefaktureer,
Therapy Session overlaps with {0},Terapiesessie oorvleuel met {0},
Therapy Sessions Overlapping,Terapiesessies oorvleuel,
Therapy Plans,Terapieplanne,

Can't render this file because it is too large.

Some files were not shown because too many files have changed in this diff Show More