Merge pull request #19940 from marination/item_manufacturer_table_dev
fix: Removed 'manufacturers' table from Item Master
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index cccced8..cf1748f 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -109,12 +109,13 @@
if not descendants: return
parent_acc_name_map = {}
- parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
+ parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
+ ["account_name", "account_number"])
for d in frappe.db.get_values('Account',
- {"company": ["in", descendants], "account_name": parent_acc_name},
+ { "company": ["in", descendants], "account_name": parent_acc_name,
+ "account_number": parent_acc_number },
["company", "name"], as_dict=True):
parent_acc_name_map[d["company"]] = d["name"]
-
if not parent_acc_name_map: return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
index deedafd..33ae454 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
@@ -15,8 +15,8 @@
with open(frappe.uploaded_file, "rb") as upfile:
fcontent = upfile.read()
else:
- from frappe.utils.file_manager import get_uploaded_content
- fname, fcontent = get_uploaded_content()
+ fcontent = frappe.local.uploaded_file
+ fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
from frappe.utils.csvutils import read_csv_content
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index acfc660..9979377 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -332,6 +332,7 @@
"label": "Reference"
},
{
+ "depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
@@ -575,7 +576,7 @@
}
],
"is_submittable": 1,
- "modified": "2019-11-06 12:59:43.151721",
+ "modified": "2019-12-08 13:02:30.016610",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index eda59ab..6133b1c 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -350,13 +350,13 @@
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
- if dt in ["Sales Invoice", "Purchase Invoice"]:
+ elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
- if dt == "Fees":
+ elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0 :
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 971d308..f73fb10 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -439,19 +440,20 @@
},
{
"default": "0",
- "depends_on": "eval:!doc.mixed_conditions",
+ "depends_on": "eval:!doc.mixed_conditions && doc.price_or_product_discount == 'Price'",
"fieldname": "same_item",
"fieldtype": "Check",
"label": "Same Item"
},
{
- "depends_on": "eval:!doc.same_item || doc.mixed_conditions",
+ "depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions",
"fieldname": "free_item",
"fieldtype": "Link",
"label": "Free Item",
"options": "Item"
},
{
+ "default": "0",
"fieldname": "free_qty",
"fieldtype": "Float",
"label": "Qty"
@@ -554,7 +556,8 @@
],
"icon": "fa fa-gift",
"idx": 1,
- "modified": "2019-10-15 12:39:40.399792",
+ "links": [],
+ "modified": "2019-12-13 15:48:48.331495",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 430dce7..b99c07e 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -34,8 +34,7 @@
def validate_duplicate_apply_on(self):
field = apply_on_dict.get(self.apply_on)
- values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)]
-
+ values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
if len(values) != len(set(values)):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
@@ -183,7 +182,7 @@
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
- get_applied_pricing_rules, get_pricing_rule_items)
+ get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
if isinstance(doc, string_types):
doc = json.loads(doc)
@@ -242,9 +241,11 @@
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details
- if (not pricing_rule.validate_applied_rule and
- pricing_rule.price_or_product_discount == "Price"):
- apply_price_discount_pricing_rule(pricing_rule, item_details, args)
+ if not pricing_rule.validate_applied_rule:
+ if pricing_rule.price_or_product_discount == "Price":
+ apply_price_discount_rule(pricing_rule, item_details, args)
+ else:
+ get_product_discount_rule(pricing_rule, item_details, doc)
item_details.has_pricing_rule = 1
@@ -294,7 +295,7 @@
'child_docname': args.get('child_docname')
})
-def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
+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)
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 637e503..87f6822 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -7,7 +7,7 @@
import frappe, copy, json
from frappe import throw, _
from six import string_types
-from frappe.utils import flt, cint, get_datetime
+from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
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
@@ -284,7 +284,7 @@
status = True
# if user has created item price against the transaction UOM
- if rule.get("uom") == args.get("uom"):
+ if args and rule.get("uom") == args.get("uom"):
conversion_factor = 1.0
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
@@ -408,7 +408,8 @@
conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
- where {conditions} """.format(conditions = conditions), values, as_dict=1)
+ where {conditions} and `tabPricing Rule`.disable = 0
+ """.format(conditions = conditions), values, as_dict=1)
if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
@@ -420,39 +421,65 @@
doc.set('apply_discount_on', d.apply_discount_on)
for field in ['additional_discount_percentage', 'discount_amount']:
- if not d.get(field): continue
-
pr_field = ('discount_percentage'
if field == 'additional_discount_percentage' else field)
+ if not d.get(pr_field): continue
+
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
doc.set(field, d.get(pr_field))
+
+ doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':
- apply_pricing_rule_for_free_items(doc, d)
+ item_details = frappe._dict({'parenttype': doc.doctype})
+ get_product_discount_rule(d, item_details, doc)
+ apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
+ doc.set_missing_values()
def get_applied_pricing_rules(item_row):
return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else [])
-def apply_pricing_rule_for_free_items(doc, pricing_rule):
- if pricing_rule.get('free_item'):
+def get_product_discount_rule(pricing_rule, item_details, doc=None):
+ free_item = (pricing_rule.free_item
+ if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code)
+
+ if not free_item:
+ frappe.throw(_("Free item not set in the pricing rule {0}")
+ .format(get_link_to_form("Pricing Rule", pricing_rule.name)))
+
+ item_details.free_item_data = {
+ 'item_code': free_item,
+ 'qty': pricing_rule.free_qty or 1,
+ 'rate': pricing_rule.free_item_rate or 0,
+ 'price_list_rate': pricing_rule.free_item_rate or 0,
+ 'is_free_item': 1
+ }
+
+ item_data = frappe.get_cached_value('Item', free_item, ['item_name',
+ 'description', 'stock_uom'], as_dict=1)
+
+ item_details.free_item_data.update(item_data)
+ item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
+ item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
+ item_details.free_item_data['uom']).get("conversion_factor", 1)
+
+ if item_details.get("parenttype") == 'Purchase Order':
+ item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
+
+ if item_details.get("parenttype") == 'Sales Order':
+ item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
+
+def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
+ if pricing_rule_args.get('item_code'):
items = [d.item_code for d in doc.items
- if d.item_code == (d.item_code
- if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
+ if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
if not items:
- doc.append('items', {
- 'item_code': pricing_rule.get('free_item'),
- 'qty': pricing_rule.get('free_qty'),
- 'uom': pricing_rule.get('free_item_uom'),
- 'rate': pricing_rule.get('free_item_rate') or 0,
- 'is_free_item': 1
- })
-
- doc.set_missing_values()
+ doc.append('items', pricing_rule_args)
def get_pricing_rule_items(pr_doc):
apply_on_data = []
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 3bb3df8..917acba 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -248,7 +248,7 @@
def set_against_expense_account(self):
against_accounts = []
for item in self.get("items"):
- if item.expense_account not in against_accounts:
+ if item.expense_account and (item.expense_account not in against_accounts):
against_accounts.append(item.expense_account)
self.against_expense_account = ",".join(against_accounts)
@@ -830,7 +830,11 @@
)
def make_gle_for_rounding_adjustment(self, gl_entries):
- if self.rounding_adjustment:
+ # if rounding adjustment in small and conversion rate is also small then
+ # base_rounding_adjustment may become zero due to small precision
+ # eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
+ # then base_rounding_adjustment becomes zero and error is thrown in GL Entry
+ if self.rounding_adjustment and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)
diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js
new file mode 100644
index 0000000..81488a2
--- /dev/null
+++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js
@@ -0,0 +1,3 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Purchase Invoice');
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 27d8233..acb0398 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-05-22 12:43:10",
"doctype": "DocType",
@@ -507,7 +508,8 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_stop_date",
"fieldtype": "Date",
- "label": "Service Stop Date"
+ "label": "Service Stop Date",
+ "no_copy": 1
},
{
"default": "0",
@@ -523,13 +525,15 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_start_date",
"fieldtype": "Date",
- "label": "Service Start Date"
+ "label": "Service Start Date",
+ "no_copy": 1
},
{
"depends_on": "enable_deferred_expense",
"fieldname": "service_end_date",
"fieldtype": "Date",
- "label": "Service End Date"
+ "label": "Service End Date",
+ "no_copy": 1
},
{
"fieldname": "reference",
@@ -766,7 +770,8 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-11-21 16:27:52.043744",
+ "links": [],
+ "modified": "2019-12-04 12:23:17.046413",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
index bc42630..a18fec6 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
@@ -1,300 +1,108 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2013-01-10 16:34:08",
- "custom": 0,
- "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2013-01-10 16:34:08",
+ "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "field_order": [
+ "title",
+ "is_default",
+ "disabled",
+ "column_break4",
+ "company",
+ "tax_category",
+ "section_break6",
+ "taxes"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "title",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title",
+ "no_copy": 1,
+ "oldfieldname": "title",
+ "oldfieldtype": "Data",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Default"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Disabled"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "taxes",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Purchase Taxes and Charges",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "purchase_tax_details",
- "oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Purchase Taxes and Charges",
+ "oldfieldname": "purchase_tax_details",
+ "oldfieldtype": "Table",
+ "options": "Purchase Taxes and Charges"
+ },
+ {
+ "fieldname": "tax_category",
+ "fieldtype": "Link",
+ "label": "Tax Category",
+ "options": "Tax Category"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-money",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-11-07 05:18:44.095798",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Purchase Taxes and Charges Template",
- "owner": "wasim@webnotestech.com",
+ ],
+ "icon": "fa fa-money",
+ "idx": 1,
+ "modified": "2019-11-25 13:05:26.220275",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Purchase Taxes and Charges Template",
+ "owner": "wasim@webnotestech.com",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Manager"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Master Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Purchase User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "Purchase User"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index c8305e3..48fa364 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,3 +1,7 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Sales Invoice');
+
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {
frm.set_query('transporter', function() {
@@ -35,4 +39,5 @@
}, __("Make"));
}
}
+
});
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 3c85210..7f4ae3c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -697,8 +697,8 @@
if (frm.doc.company)
{
frappe.call({
- method:"frappe.contacts.doctype.address.address.get_default_address",
- args:{ doctype:'Company',name:frm.doc.company},
+ method:"erpnext.setup.doctype.company.company.get_default_company_address",
+ args:{name:frm.doc.company, existing_address: frm.doc.company_address},
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)
@@ -789,22 +789,21 @@
method: "frappe.client.get_value",
args:{
doctype: "Patient",
- filters: {"name": frm.doc.patient},
+ filters: {
+ "name": frm.doc.patient
+ },
fieldname: "customer"
},
- callback:function(patient_customer) {
- if(patient_customer){
- frm.set_value("customer", patient_customer.message.customer);
- frm.refresh_fields();
+ callback:function(r) {
+ if(r && r.message.customer){
+ frm.set_value("customer", r.message.customer);
}
}
});
}
- else{
- frm.set_value("customer", '');
- }
}
},
+
refresh: function(frm) {
if (frappe.boot.active_domains.includes("Healthcare")){
frm.set_df_property("patient", "hidden", 0);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 70a80ca..0f4d445 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -535,9 +535,7 @@
for i in dic:
if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes':
for d in self.get('items'):
- is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
- if (d.item_code and is_stock_item == 1\
- and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
+ if (d.item_code and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
@@ -953,7 +951,7 @@
)
def make_gle_for_rounding_adjustment(self, gl_entries):
- if flt(self.rounding_adjustment, self.precision("rounding_adjustment")):
+ if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)
@@ -1048,13 +1046,18 @@
continue
for serial_no in item.serial_no.split("\n"):
- sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no,
- ["sales_invoice", "item_code"])
- if sales_invoice and item_code == item.item_code and self.name != sales_invoice:
- sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
+ serial_no_details = frappe.db.get_value("Serial No", serial_no,
+ ["sales_invoice", "item_code"], as_dict=1)
+
+ if not serial_no_details:
+ continue
+
+ if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
+ and self.name != serial_no_details.sales_invoice:
+ sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
- .format(serial_no, sales_invoice)))
+ .format(serial_no, serial_no_details.sales_invoice)))
def update_project(self):
if self.project:
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 779ac4f..b2294e4 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-06-04 11:02:19",
"doctype": "DocType",
@@ -484,7 +485,8 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_stop_date",
"fieldtype": "Date",
- "label": "Service Stop Date"
+ "label": "Service Stop Date",
+ "no_copy": 1
},
{
"default": "0",
@@ -500,13 +502,15 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_start_date",
"fieldtype": "Date",
- "label": "Service Start Date"
+ "label": "Service Start Date",
+ "no_copy": 1
},
{
"depends_on": "enable_deferred_revenue",
"fieldname": "service_end_date",
"fieldtype": "Date",
- "label": "Service End Date"
+ "label": "Service End Date",
+ "no_copy": 1
},
{
"collapsible": 1,
@@ -783,7 +787,8 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-16 16:36:46.527606",
+ "links": [],
+ "modified": "2019-12-04 12:22:38.517710",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
index 29e15d1..19781bd 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -1,299 +1,119 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2013-01-10 16:34:09",
- "custom": 0,
- "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2013-01-10 16:34:09",
+ "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "is_default",
+ "disabled",
+ "column_break_3",
+ "company",
+ "tax_category",
+ "section_break_5",
+ "taxes"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "title",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title",
+ "no_copy": 1,
+ "oldfieldname": "title",
+ "oldfieldtype": "Data",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Default"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "* Will be calculated in the transaction.",
- "fieldname": "taxes",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Taxes and Charges",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "other_charges",
- "oldfieldtype": "Table",
- "options": "Sales Taxes and Charges",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "description": "* Will be calculated in the transaction.",
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Sales Taxes and Charges",
+ "oldfieldname": "other_charges",
+ "oldfieldtype": "Table",
+ "options": "Sales Taxes and Charges"
+ },
+ {
+ "fieldname": "tax_category",
+ "fieldtype": "Link",
+ "label": "Tax Category",
+ "options": "Tax Category"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-money",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-11-07 05:18:41.743257",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Sales Taxes and Charges Template",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-money",
+ "idx": 1,
+ "modified": "2019-11-25 13:06:03.279099",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Taxes and Charges Template",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 1,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Master Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 59936d5..156f218 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -23,7 +23,7 @@
@frappe.whitelist()
def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True,
- party_address=None, shipping_address=None, pos_profile=None):
+ party_address=None, company_address=None, shipping_address=None, pos_profile=None):
if not party:
return {}
@@ -31,14 +31,14 @@
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
return _get_party_details(party, account, party_type,
company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions,
- fetch_payment_terms_template, party_address, shipping_address, pos_profile)
+ fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile)
def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
- fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None):
+ fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None):
- out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
- party = out[party_type.lower()]
+ party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
+ party = party_details[party_type.lower()]
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
@@ -46,76 +46,81 @@
party = frappe.get_doc(party_type, party)
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
- party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address)
- set_contact_details(out, party, party_type)
- set_other_values(out, party, party_type)
- set_price_list(out, party, party_type, price_list, pos_profile)
+ party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
+ set_contact_details(party_details, party, party_type)
+ set_other_values(party_details, party, party_type)
+ set_price_list(party_details, party, party_type, price_list, pos_profile)
- out["tax_category"] = get_address_tax_category(party.get("tax_category"),
+ party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
party_address, shipping_address if party_type != "Supplier" else party_address)
- out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
- customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category,
- billing_address=party_address, shipping_address=shipping_address)
+
+ if not party_details.get("taxes_and_charges"):
+ party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
+ customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
+ billing_address=party_address, shipping_address=shipping_address)
if fetch_payment_terms_template:
- out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
+ party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
- if not out.get("currency"):
- out["currency"] = currency
+ if not party_details.get("currency"):
+ party_details["currency"] = currency
# sales team
if party_type=="Customer":
- out["sales_team"] = [{
+ party_details["sales_team"] = [{
"sales_person": d.sales_person,
"allocated_percentage": d.allocated_percentage or None
} for d in party.get("sales_team")]
# supplier tax withholding category
if party_type == "Supplier" and party:
- out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
+ party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
- return out
+ return party_details
-def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None):
+def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None):
billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address"
- out[billing_address_field] = party_address or get_default_address(party_type, party.name)
+ party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
- out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
+ party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]))
# address display
- out.address_display = get_address_display(out[billing_address_field])
+ party_details.address_display = get_address_display(party_details[billing_address_field])
# shipping address
if party_type in ["Customer", "Lead"]:
- out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
- out.shipping_address = get_address_display(out["shipping_address_name"])
+ party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
+ party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
if doctype:
- out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
+ party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name))
- if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
- out.update(get_company_address(company))
- if out.company_address:
- out.update(get_fetch_values(doctype, 'company_address', out.company_address))
- get_regional_address_details(out, doctype, company)
+ if company_address:
+ party_details.update({'company_address': company_address})
+ else:
+ party_details.update(get_company_address(company))
- elif doctype and doctype == "Purchase Invoice":
- out.update(get_company_address(company))
- if out.company_address:
- out["shipping_address"] = shipping_address or out["company_address"]
- out.shipping_address_display = get_address_display(out["shipping_address"])
- out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address))
- get_regional_address_details(out, doctype, company)
+ if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']:
+ if party_details.company_address:
+ party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address))
+ get_regional_address_details(party_details, doctype, company)
- return out.get(billing_address_field), out.shipping_address_name
+ elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
+ if party_details.company_address:
+ party_details["shipping_address"] = shipping_address or party_details["company_address"]
+ party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
+ party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address))
+ get_regional_address_details(party_details, doctype, company)
+
+ return party_details.get(billing_address_field), party_details.shipping_address_name
@erpnext.allow_regional
-def get_regional_address_details(out, doctype, company):
+def get_regional_address_details(party_details, doctype, company):
pass
-def set_contact_details(out, party, party_type):
- out.contact_person = get_default_contact(party_type, party.name)
+def set_contact_details(party_details, party, party_type):
+ party_details.contact_person = get_default_contact(party_type, party.name)
- if not out.contact_person:
- out.update({
+ if not party_details.contact_person:
+ party_details.update({
"contact_person": None,
"contact_display": None,
"contact_email": None,
@@ -125,22 +130,22 @@
"contact_department": None
})
else:
- out.update(get_contact_details(out.contact_person))
+ party_details.update(get_contact_details(party_details.contact_person))
-def set_other_values(out, party, party_type):
+def set_other_values(party_details, party, party_type):
# copy
if party_type=="Customer":
to_copy = ["customer_name", "customer_group", "territory", "language"]
else:
to_copy = ["supplier_name", "supplier_group", "language"]
for f in to_copy:
- out[f] = party.get(f)
+ party_details[f] = party.get(f)
# fields prepended with default in Customer doctype
for f in ['currency'] \
+ (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
if party.get("default_" + f):
- out[f] = party.get("default_" + f)
+ party_details[f] = party.get("default_" + f)
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
@@ -155,7 +160,7 @@
return None
-def set_price_list(out, party, party_type, given_price_list, pos=None):
+def set_price_list(party_details, party, party_type, given_price_list, pos=None):
# price list
price_list = get_permitted_documents('Price List')
@@ -173,9 +178,9 @@
price_list = get_default_price_list(party) or given_price_list
if price_list:
- out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
+ party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
- out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
+ party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 8eb670de..b1f427c 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -101,6 +101,11 @@
"options": "Supplier Group"
},
{
+ "fieldname":"based_on_payment_terms",
+ "label": __("Based On Payment Terms"),
+ "fieldtype": "Check",
+ },
+ {
"fieldname":"tax_id",
"label": __("Tax Id"),
"fieldtype": "Data",
diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
index 5f0fdc9..4a9f1b0 100644
--- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
+++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
@@ -88,6 +88,11 @@
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
+ },
+ {
+ "fieldname":"based_on_payment_terms",
+ "label": __("Based On Payment Terms"),
+ "fieldtype": "Check",
}
],
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 14906f2..2c53f6e 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -60,6 +60,7 @@
def get_data(self):
self.get_gl_entries()
+ self.get_sales_invoices_or_customers_based_on_sales_person()
self.voucher_balance = OrderedDict()
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
@@ -103,12 +104,18 @@
def get_invoices(self, gle):
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
- self.invoices.add(gle.voucher_no)
+ if self.filters.get("sales_person"):
+ if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \
+ or gle.party in self.sales_person_records.get("Customer", []):
+ self.invoices.add(gle.voucher_no)
+ else:
+ self.invoices.add(gle.voucher_no)
def update_voucher_balance(self, gle):
# get the row where this balance needs to be updated
# if its a payment, it will return the linked invoice or will be considered as advance
row = self.get_voucher_balance(gle)
+ if not row: return
# gle_balance will be the total "debit - credit" for receivable type reports and
# and vice-versa for payable type reports
gle_balance = self.get_gle_balance(gle)
@@ -129,8 +136,13 @@
row.paid -= gle_balance
def get_voucher_balance(self, gle):
- voucher_balance = None
+ if self.filters.get("sales_person"):
+ against_voucher = gle.against_voucher or gle.voucher_no
+ if not (gle.party in self.sales_person_records.get("Customer", []) or \
+ against_voucher in self.sales_person_records.get("Sales Invoice", [])):
+ return
+ voucher_balance = None
if gle.against_voucher:
# find invoice
against_voucher = gle.against_voucher
@@ -318,7 +330,7 @@
self.append_payment_term(row, d, term)
def append_payment_term(self, row, d, term):
- if self.filters.get("customer") and d.currency == d.party_account_currency:
+ if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency:
invoiced = d.payment_amount
else:
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
@@ -512,6 +524,22 @@
order by posting_date, party"""
.format(select_fields, conditions), values, as_dict=True)
+ def get_sales_invoices_or_customers_based_on_sales_person(self):
+ if self.filters.get("sales_person"):
+ lft, rgt = frappe.db.get_value("Sales Person",
+ self.filters.get("sales_person"), ["lft", "rgt"])
+
+ records = frappe.db.sql("""
+ select distinct parent, parenttype
+ from `tabSales Team` steam
+ where parenttype in ('Customer', 'Sales Invoice')
+ and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
+ """, (lft, rgt), as_dict=1)
+
+ self.sales_person_records = frappe._dict()
+ for d in records:
+ self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
+
def prepare_conditions(self):
conditions = [""]
values = [self.party_type, self.filters.report_date]
@@ -564,16 +592,6 @@
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
values.append(self.filters.get("sales_partner"))
- if self.filters.get("sales_person"):
- lft, rgt = frappe.db.get_value("Sales Person",
- self.filters.get("sales_person"), ["lft", "rgt"])
-
- conditions.append("""exists(select name from `tabSales Team` steam where
- steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
- and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
- or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
- or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
-
def add_supplier_filters(self, conditions, values):
if self.filters.get("supplier_group"):
conditions.append("""party in (select name from tabSupplier
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
index 0120608..d54824b 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
@@ -106,6 +106,11 @@
"label": __("Sales Person"),
"fieldtype": "Link",
"options": "Sales Person"
+ },
+ {
+ "fieldname":"based_on_payment_terms",
+ "label": __("Based On Payment Terms"),
+ "fieldtype": "Check",
}
],
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 8955830..b607c0f 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -36,7 +36,7 @@
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
- if party_dict.outstanding <= 0:
+ if party_dict.outstanding == 0:
continue
row = frappe._dict()
diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html
index 4081723..50947ec 100644
--- a/erpnext/accounts/report/financial_statements.html
+++ b/erpnext/accounts/report/financial_statements.html
@@ -1,5 +1,6 @@
{%
var report_columns = report.get_columns_for_print();
+ report_columns = report_columns.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application."));
@@ -15,34 +16,35 @@
height: 37px;
}
</style>
-{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %}
-{% if(letterhead) { %}
-<div style="margin-bottom: 7px;" class="text-center">
- {%= frappe.boot.letter_heads[letterhead].header %}
-</div>
-{% } %}
+
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h3 class="text-center">{%= filters.company %}</h3>
+
{% if 'cost_center' in filters %}
<h3 class="text-center">{%= filters.cost_center %}</h3>
{% endif %}
+
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
-<h5 class="text-center">{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} </h4>
+<h5 class="text-center">
+ {%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
+</h5>
{% if (filters.from_date) { %}
- <h4 class="text-center">{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}</h3>
+ <h5 class="text-center">
+ {%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
+ </h5>
{% } %}
<hr>
<table class="table table-bordered">
<thead>
<tr>
- <th style="width: {%= 100 - (report_columns.length - 2) * 13 %}%"></th>
- {% for(var i=2, l=report_columns.length; i<l; i++) { %}
+ <th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
+ {% for (let i=1, l=report_columns.length; i<l; i++) { %}
<th class="text-right">{%= report_columns[i].label %}</th>
{% } %}
</tr>
</thead>
<tbody>
- {% for(var j=0, k=data.length-1; j<k; j++) { %}
+ {% for(let j=0, k=data.length-1; j<k; j++) { %}
{%
var row = data[j];
var row_class = data[j].parent_account ? "" : "financial-statements-important";
@@ -52,11 +54,11 @@
<td>
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name %}</span>
</td>
- {% for(var i=2, l=report_columns.length; i<l; i++) { %}
+ {% for(let i=1, l=report_columns.length; i<l; i++) { %}
<td class="text-right">
- {% var fieldname = report_columns[i].fieldname; %}
+ {% const fieldname = report_columns[i].fieldname; %}
{% if (!is_null(row[fieldname])) { %}
- {%= format_currency(row[fieldname], filters.presentation_currency) %}
+ {%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
{% } %}
</td>
{% } %}
@@ -64,4 +66,6 @@
{% } %}
</tbody>
</table>
-<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
+<p class="text-right text-muted">
+ Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
+</p>
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 3c8de60..40d5682 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -264,8 +264,8 @@
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
total_row = {
- "account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
- "account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
+ "account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
+ "account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"currency": company_currency
}
diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
index bd2c34b..3e47906 100644
--- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
+++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
@@ -18,14 +18,17 @@
return columns, data
def get_data(filters, show_party_name):
- party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
+ if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
+ party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
if filters.get('party_type') == 'Student':
party_name_field = 'first_name'
elif filters.get('party_type') == 'Shareholder':
party_name_field = 'title'
+ else:
+ party_name_field = 'name'
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
- parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
+ parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
filters = party_filters, order_by="name")
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
opening_balances = get_opening_balances(filters)
@@ -70,7 +73,7 @@
# totals
for col in total_row:
total_row[col] += row.get(col)
-
+
row.update({
"currency": company_currency
})
@@ -78,7 +81,7 @@
has_value = False
if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit):
has_value =True
-
+
if cint(filters.show_zero_values) or has_value:
data.append(row)
@@ -94,9 +97,9 @@
def get_opening_balances(filters):
gle = frappe.db.sql("""
- select party, sum(debit) as opening_debit, sum(credit) as opening_credit
+ select party, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
- where company=%(company)s
+ where company=%(company)s
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
group by party""", {
@@ -114,11 +117,11 @@
def get_balances_within_period(filters):
gle = frappe.db.sql("""
- select party, sum(debit) as debit, sum(credit) as credit
+ select party, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry`
- where company=%(company)s
+ where company=%(company)s
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
- and posting_date >= %(from_date)s and posting_date <= %(to_date)s
+ and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and ifnull(is_opening, 'No') = 'No'
group by party""", {
"company": filters.company,
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 94697be..89c8467 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -569,7 +569,7 @@
warehouse_account = get_warehouse_account_map(company)
- account_balance = get_balance_on(account, posting_date, in_account_currency=False)
+ account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
if wh_details.account == account and not wh_details.is_group]
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 6b3f2c7..f6a7fa2 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -144,6 +144,10 @@
frm.set_df_property('purchase_invoice', 'read_only', 1);
frm.set_df_property('purchase_receipt', 'read_only', 1);
}
+ else if (frm.doc.is_existing_asset) {
+ frm.toggle_reqd('purchase_receipt', 0);
+ frm.toggle_reqd('purchase_invoice', 0);
+ }
else if (frm.doc.purchase_receipt) {
// if purchase receipt link is set then set PI disabled
frm.toggle_reqd('purchase_invoice', 0);
@@ -256,6 +260,7 @@
},
is_existing_asset: function(frm) {
+ frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
},
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 40f1e1e..3e7f683 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -517,15 +517,18 @@
asset.set_status('Out of Order')
def make_post_gl_entry():
- if not is_cwip_accounting_enabled(self.asset_category):
- return
- assets = frappe.db.sql_list(""" select name from `tabAsset`
- where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate())
+ asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting'])
- for asset in assets:
- doc = frappe.get_doc('Asset', asset)
- doc.make_gl_entries()
+ for asset_category in asset_categories:
+ if cint(asset_category.enable_cwip_accounting):
+ assets = frappe.db.sql_list(""" select name from `tabAsset`
+ where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0
+ and available_for_use_date = %s""", (asset_category.name, nowdate()))
+
+ for asset in assets:
+ doc = frappe.get_doc('Asset', asset)
+ doc.make_gl_entries()
def get_asset_naming_series():
meta = frappe.get_meta('Asset')
@@ -607,13 +610,19 @@
if asset:
account = get_asset_category_account(account_name, asset=asset,
asset_category = asset_category, company = company)
+
+ if not asset and not account:
+ account = get_asset_category_account(account_name, asset_category = asset_category, company = company)
if not account:
account = frappe.get_cached_value('Company', company, account_name)
if not account:
- frappe.throw(_("Set {0} in asset category {1} or company {2}")
- .format(account_name.replace('_', ' ').title(), asset_category, company))
+ if not asset_category:
+ frappe.throw(_("Set {0} in company {2}").format(account_name.replace('_', ' ').title(), company))
+ else:
+ frappe.throw(_("Set {0} in asset category {1} or company {2}")
+ .format(account_name.replace('_', ' ').title(), asset_category, company))
return account
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index 426caaa..8c737d0 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -26,5 +26,11 @@
fieldtype: "Link",
options: "Finance Book"
},
+ {
+ fieldname:"date",
+ label: __("Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.get_today()
+ },
]
};
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index f395499..57b68b4 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cstr
+from frappe.utils import cstr, today, flt
def execute(filters=None):
filters = frappe._dict(filters or {})
@@ -86,8 +86,8 @@
"width": 90
},
{
- "label": _("Current Value"),
- "fieldname": "current_value",
+ "label": _("Asset Value"),
+ "fieldname": "asset_value",
"options": "Currency",
"width": 90
},
@@ -114,7 +114,7 @@
data = []
conditions = get_conditions(filters)
- current_value_map = get_finance_book_value_map(filters.finance_book)
+ depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book)
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
@@ -125,7 +125,9 @@
"available_for_use_date", "status", "purchase_invoice"])
for asset in assets_record:
- if current_value_map.get(asset.name) is not None:
+ asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
+ - flt(depreciation_amount_map.get(asset.name))
+ if asset_value:
row = {
"asset_id": asset.name,
"asset_name": asset.asset_name,
@@ -138,19 +140,24 @@
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
- "current_value": current_value_map.get(asset.name)
+ "asset_value": asset_value
}
data.append(row)
return data
-def get_finance_book_value_map(finance_book=''):
+def get_finance_book_value_map(date, finance_book=''):
+ if not date:
+ date = today()
return frappe._dict(frappe.db.sql(''' Select
- parent, value_after_depreciation
- FROM `tabAsset Finance Book`
+ parent, SUM(depreciation_amount)
+ FROM `tabDepreciation Schedule`
WHERE
- parentfield='finance_books'
- AND ifnull(finance_book, '')=%s''', cstr(finance_book)))
+ parentfield='schedules'
+ AND schedule_date<=%s
+ AND journal_entry IS NOT NULL
+ AND ifnull(finance_book, '')=%s
+ GROUP BY parent''', (date, cstr(finance_book))))
def get_purchase_receipt_supplier_map():
return frappe._dict(frappe.db.sql(''' Select
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index c5fa98d..7b5e5c5 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -18,6 +18,7 @@
return {
filters: {
"company": frm.doc.company,
+ "name": ['!=', frm.doc.supplier_warehouse],
"is_group": 0
}
}
@@ -283,6 +284,8 @@
})
}
+ me.dialog.get_field('sub_con_rm_items').check_all_rows()
+
me.dialog.show()
this.dialog.set_primary_action(__('Transfer'), function() {
me.values = me.dialog.get_values();
diff --git a/erpnext/buying/doctype/purchase_order/regional/india.js b/erpnext/buying/doctype/purchase_order/regional/india.js
new file mode 100644
index 0000000..42d3995
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/regional/india.js
@@ -0,0 +1,3 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Purchase Order');
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 4506db6..08f5d8b 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -17,6 +17,8 @@
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.controllers.accounts_controller import update_child_qty_rate
from erpnext.controllers.status_updater import OverAllowanceError
+from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
+
class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self):
@@ -519,47 +521,62 @@
def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code)
+ make_item('Sub Contracted Raw Material 1', {
+ 'is_stock_item': 1,
+ 'is_sub_contracted_item': 1
+ })
update_backflush_based_on("Material Transferred for Subcontract")
- po = create_purchase_order(item_code=item_code, qty=1,
+
+ order_qty = 5
+ po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
- make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
+ make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
- rm_item = [
- {"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
- "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
+ rm_items = [
+ {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
+ "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
- "qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
+ "qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
- "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
+ "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
+ {'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
+ 'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
- rm_item_string = json.dumps(rm_item)
+ rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
- se.append('items', {
- 'item_code': "Test Extra Item 2",
- "qty": 1,
- "rate": 100,
- "s_warehouse": "_Test Warehouse - _TC",
- "t_warehouse": "_Test Warehouse 1 - _TC"
- })
- se.set_missing_values()
se.submit()
pr = make_purchase_receipt(po.name)
+
+ received_qty = 2
+ # partial receipt
+ pr.get('items')[0].qty = received_qty
pr.save()
pr.submit()
- se_items = sorted([d.item_code for d in se.get('items')])
- supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
+ transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
+ issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
- self.assertEquals(se_items, supplied_items)
+ self.assertEquals(transferred_items, issued_items)
+ self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
+
+
+ transferred_rm_map = frappe._dict()
+ for item in rm_items:
+ transferred_rm_map[item.get('rm_item_code')] = item
+
+ for item in pr.get('supplied_items'):
+ self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
+
update_backflush_based_on("BOM")
def test_advance_payment_entry_unlink_against_purchase_order(self):
@@ -605,6 +622,27 @@
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
+
+ def test_po_optional_blanket_order(self):
+ """
+ Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
+ Second Purchase Order should not add on to Blanket Orders Ordered Quantity.
+ """
+
+ bo = make_blanket_order(blanket_order_type = "Purchasing", quantity = 10, rate = 10)
+
+ po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1)
+ po_doc = frappe.get_doc('Purchase Order', po.get('name'))
+ # To test if the PO has a Blanket Order
+ self.assertTrue(po_doc.items[0].blanket_order)
+
+ po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0)
+ po_doc = frappe.get_doc('Purchase Order', po.get('name'))
+ # To test if the PO does NOT have a Blanket Order
+ self.assertEqual(po_doc.items[0].blanket_order, None)
+
+
+
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
@@ -678,7 +716,8 @@
"qty": args.qty or 10,
"rate": args.rate or 500,
"schedule_date": add_days(nowdate(), 1),
- "include_exploded_items": args.get('include_exploded_items', 1)
+ "include_exploded_items": args.get('include_exploded_items', 1),
+ "against_blanket_order": args.against_blanket_order
})
if not args.do_not_save:
po.insert()
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index c409c1f..6768dfa 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-05-24 19:29:06",
"doctype": "DocType",
@@ -67,6 +68,7 @@
"supplier_quotation",
"supplier_quotation_item",
"col_break5",
+ "against_blanket_order",
"blanket_order",
"blanket_order_rate",
"item_group",
@@ -511,6 +513,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:doc.against_blanket_order",
"fieldname": "blanket_order",
"fieldtype": "Link",
"label": "Blanket Order",
@@ -518,6 +521,7 @@
"options": "Blanket Order"
},
{
+ "depends_on": "eval:doc.against_blanket_order",
"fieldname": "blanket_order_rate",
"fieldtype": "Currency",
"label": "Blanket Order Rate",
@@ -703,6 +707,12 @@
},
{
"default": "0",
+ "fieldname": "against_blanket_order",
+ "fieldtype": "Check",
+ "label": "Against Blanket Order"
+ },
+ {
+ "default": "0",
"fetch_from": "item_code.is_fixed_asset",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
@@ -712,7 +722,8 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-11-07 17:19:12.090355",
+ "links": [],
+ "modified": "2019-12-06 13:17:12.142799",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json b/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json
similarity index 78%
rename from erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json
rename to erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json
index 006d139..d3adcb7 100644
--- a/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json
+++ b/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json
@@ -3,18 +3,18 @@
"app": "ERPNext",
"creation": "2019-11-15 14:45:32.626641",
"docstatus": 0,
- "doctype": "Setup Wizard Slide",
+ "doctype": "Onboarding Slide",
"domains": [],
"help_links": [
{
- "label": "Supplier",
+ "label": "Learn More",
"video_id": "zsrrVDk6VBs"
}
],
"idx": 0,
- "image_src": "/assets/erpnext/images/illustrations/supplier.png",
+ "image_src": "/assets/erpnext/images/illustrations/supplier-onboard.png",
"max_count": 3,
- "modified": "2019-11-26 18:26:25.498325",
+ "modified": "2019-12-03 22:53:50.552445",
"modified_by": "Administrator",
"name": "Add A Few Suppliers",
"owner": "Administrator",
@@ -44,6 +44,5 @@
],
"slide_order": 50,
"slide_title": "Add A Few Suppliers",
- "slide_type": "Create",
- "submit_method": ""
+ "slide_type": "Create"
}
\ No newline at end of file
diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py
index ab75f21..08711fc 100644
--- a/erpnext/config/accounts.py
+++ b/erpnext/config/accounts.py
@@ -197,6 +197,11 @@
"name": "Bank Reconciliation Statement",
"is_query_report": True,
"doctype": "Journal Entry"
+ },{
+ "type": "page",
+ "name": "bank-reconciliation",
+ "label": _("Bank Reconciliation"),
+ "icon": "fa fa-bar-chart"
},
{
"type": "report",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1f8b663..6150516 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -61,7 +61,6 @@
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
def validate(self):
-
if not self.get('is_return'):
self.validate_qty_is_not_zero()
@@ -100,11 +99,23 @@
if self.is_return:
self.validate_qty()
+ else:
+ self.validate_deferred_start_and_end_date()
validate_regional(self)
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
+ def validate_deferred_start_and_end_date(self):
+ for d in self.items:
+ if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
+ if not (d.service_start_date and d.service_end_date):
+ frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
+ elif getdate(d.service_start_date) > getdate(d.service_end_date):
+ frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
+ elif getdate(self.posting_date) > getdate(d.service_end_date):
+ frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
+
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
self.set_due_date()
@@ -308,8 +319,8 @@
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
- elif pricing_rule_args.get('free_item'):
- apply_pricing_rule_for_free_items(self, pricing_rule_args)
+ elif pricing_rule_args.get('free_item_data'):
+ apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
elif pricing_rule_args.get("validate_applied_rule"):
for pricing_rule in get_applied_pricing_rules(item):
@@ -415,9 +426,10 @@
return gl_dict
def validate_qty_is_not_zero(self):
- for item in self.items:
- if not item.qty:
- frappe.throw(_("Item quantity can not be zero"))
+ if self.doctype != "Purchase Receipt":
+ for item in self.items:
+ if not item.qty:
+ frappe.throw(_("Item quantity can not be zero"))
def validate_account_currency(self, account, account_currency=None):
valid_currency = [self.company_currency]
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index d12643a..3ec7aff 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -221,7 +221,7 @@
"backflush_raw_materials_of_subcontract_based_on")
if (self.doctype == 'Purchase Receipt' and
backflush_raw_materials_based_on != 'BOM'):
- self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
+ self.update_raw_materials_supplied_based_on_stock_entries()
else:
for item in self.get("items"):
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -241,41 +241,95 @@
if self.is_subcontracted == "No" and self.get("supplied_items"):
self.set('supplied_items', [])
- def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
- self.set(raw_material_table, [])
- purchase_orders = [d.purchase_order for d in self.items]
- if purchase_orders:
- items = get_subcontracted_raw_materials_from_se(purchase_orders)
- backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
+ def update_raw_materials_supplied_based_on_stock_entries(self):
+ self.set('supplied_items', [])
- for d in items:
- qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
- rm = self.append(raw_material_table, {})
- rm.rm_item_code = d.item_code
- rm.item_name = d.item_name
- rm.main_item_code = d.main_item_code
- rm.description = d.description
- rm.stock_uom = d.stock_uom
- rm.required_qty = qty
- rm.consumed_qty = qty
- rm.serial_no = d.serial_no
- rm.batch_no = d.batch_no
+ purchase_orders = set([d.purchase_order for d in self.items])
- # get raw materials rate
- from erpnext.stock.utils import get_incoming_rate
- rm.rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": self.supplier_warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "qty": -1 * qty,
- "serial_no": rm.serial_no
- })
- if not rm.rate:
- rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
- self.doctype, self.name, currency=self.company_currency, company = self.company)
+ # qty of raw materials backflushed (for each item per purchase order)
+ backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
- rm.amount = qty * flt(rm.rate)
+ # qty of "finished good" item yet to be received
+ qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
+
+ for item in self.get('items'):
+ # reset raw_material cost
+ item.rm_supp_cost = 0
+
+ # qty of raw materials transferred to the supplier
+ transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
+
+ non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
+
+ item_key = '{}{}'.format(item.item_code, item.purchase_order)
+
+ fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
+
+ raw_material_data = backflushed_raw_materials_map.get(item_key, {})
+
+ consumed_qty = raw_material_data.get('qty', 0)
+ consumed_serial_nos = raw_material_data.get('serial_nos', '')
+ consumed_batch_nos = raw_material_data.get('batch_nos', '')
+
+ transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
+ backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
+
+ for raw_material in transferred_raw_materials + non_stock_items:
+ transferred_qty = raw_material.qty
+
+ rm_qty_to_be_consumed = transferred_qty - consumed_qty
+
+ # backflush all remaining transferred qty in the last Purchase Receipt
+ if fg_yet_to_be_received == item.qty:
+ qty = rm_qty_to_be_consumed
+ else:
+ qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
+
+ if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
+ qty = frappe.utils.ceil(qty)
+
+ if qty > rm_qty_to_be_consumed:
+ qty = rm_qty_to_be_consumed
+
+ if not qty: continue
+
+ if raw_material.serial_nos:
+ set_serial_nos(raw_material, consumed_serial_nos, qty)
+
+ if raw_material.batch_nos:
+ batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
+ qty, transferred_batch_qty_map, backflushed_batch_qty_map)
+ for batch_data in batches_qty:
+ qty = batch_data['qty']
+ raw_material.batch_no = batch_data['batch']
+ self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+ else:
+ self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+
+ def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
+ rm = self.append('supplied_items', {})
+ rm.update(raw_material_data)
+
+ rm.required_qty = qty
+ rm.consumed_qty = qty
+
+ if not raw_material_data.get('non_stock_item'):
+ from erpnext.stock.utils import get_incoming_rate
+ rm.rate = get_incoming_rate({
+ "item_code": raw_material_data.rm_item_code,
+ "warehouse": self.supplier_warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "qty": -1 * qty,
+ "serial_no": rm.serial_no
+ })
+
+ if not rm.rate:
+ rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
+ self.doctype, self.name, currency=self.company_currency, company=self.company)
+
+ rm.amount = qty * flt(rm.rate)
+ fg_item_doc.rm_supp_cost += rm.amount
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
exploded_item = 1
@@ -387,9 +441,11 @@
item_codes = list(set(item.item_code for item in
self.get("items")))
if item_codes:
- self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
- from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
- (", ".join((["%s"]*len(item_codes))),), item_codes)]
+ items = frappe.get_all('Item', filters={
+ 'name': ['in', item_codes],
+ 'is_sub_contracted_item': 1
+ })
+ self._sub_contracted_items = [item.name for item in items]
return self._sub_contracted_items
@@ -722,28 +778,72 @@
return bom_items
-def get_subcontracted_raw_materials_from_se(purchase_orders):
- return frappe.db.sql("""
- select
- sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
- sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
- from `tabStock Entry` se,`tabStock Entry Detail` sed
- where
- se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
- and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
- group by sed.item_code, sed.t_warehouse
- """ % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
+def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
+ common_query = """
+ SELECT
+ sed.item_code AS rm_item_code,
+ SUM(sed.qty) AS qty,
+ sed.description,
+ sed.stock_uom,
+ sed.subcontracted_item AS main_item_code,
+ {serial_no_concat_syntax} AS serial_nos,
+ {batch_no_concat_syntax} AS batch_nos
+ FROM `tabStock Entry` se,`tabStock Entry Detail` sed
+ WHERE
+ se.name = sed.parent
+ AND se.docstatus=1
+ AND se.purpose='Send to Subcontractor'
+ AND se.purchase_order = %s
+ AND IFNULL(sed.t_warehouse, '') != ''
+ AND sed.subcontracted_item = %s
+ GROUP BY sed.item_code, sed.subcontracted_item
+ """
+ raw_materials = frappe.db.multisql({
+ 'mariadb': common_query.format(
+ serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
+ batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
+ ),
+ 'postgres': common_query.format(
+ serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
+ batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
+ )
+ }, (purchase_order, fg_item), as_dict=1)
-def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
- return frappe._dict(frappe.db.sql("""
- select
- prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
- from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
- where
- pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
- and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
- group by prsi.rm_item_code
- """ % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
+ return raw_materials
+
+def get_backflushed_subcontracted_raw_materials(purchase_orders):
+ common_query = """
+ SELECT
+ CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
+ SUM(prsi.consumed_qty) AS qty,
+ {serial_no_concat_syntax} AS serial_nos,
+ {batch_no_concat_syntax} AS batch_nos
+ FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
+ WHERE
+ pr.name = pri.parent
+ AND pr.name = prsi.parent
+ AND pri.purchase_order IN %s
+ AND pri.item_code = prsi.main_item_code
+ AND pr.docstatus = 1
+ GROUP BY prsi.rm_item_code, pri.purchase_order
+ """
+
+ backflushed_raw_materials = frappe.db.multisql({
+ 'mariadb': common_query.format(
+ serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
+ batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
+ ),
+ 'postgres': common_query.format(
+ serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
+ batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
+ )
+ }, (purchase_orders, ), as_dict=1)
+
+ backflushed_raw_materials_map = frappe._dict()
+ for item in backflushed_raw_materials:
+ backflushed_raw_materials_map.setdefault(item.item_key, item)
+
+ return backflushed_raw_materials_map
def get_asset_item_details(asset_items):
asset_items_data = {}
@@ -776,3 +876,125 @@
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
frappe.throw(error_message)
+
+def get_qty_to_be_received(purchase_orders):
+ return frappe._dict(frappe.db.sql("""
+ SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
+ SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
+ FROM `tabPurchase Order Item` poi
+ WHERE
+ poi.`parent` in %s
+ GROUP BY poi.`item_code`, poi.`parent`
+ HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
+ """, (purchase_orders)))
+
+def get_non_stock_items(purchase_order, fg_item_code):
+ return frappe.db.sql("""
+ SELECT
+ pois.main_item_code,
+ pois.rm_item_code,
+ item.description,
+ pois.required_qty AS qty,
+ pois.rate,
+ 1 as non_stock_item,
+ pois.stock_uom
+ FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
+ WHERE
+ pois.`rm_item_code` = item.`name`
+ AND item.is_stock_item = 0
+ AND pois.`parent` = %s
+ AND pois.`main_item_code` = %s
+ """, (purchase_order, fg_item_code), as_dict=1)
+
+
+def set_serial_nos(raw_material, consumed_serial_nos, qty):
+ serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
+ set(get_serial_nos(consumed_serial_nos))
+ if serial_nos and qty <= len(serial_nos):
+ raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
+
+def get_transferred_batch_qty_map(purchase_order, fg_item):
+ # returns
+ # {
+ # (item_code, fg_code): {
+ # batch1: 10, # qty
+ # batch2: 16
+ # },
+ # }
+ transferred_batch_qty_map = {}
+ transferred_batches = frappe.db.sql("""
+ SELECT
+ sed.batch_no,
+ SUM(sed.qty) AS qty,
+ sed.item_code
+ FROM `tabStock Entry` se,`tabStock Entry Detail` sed
+ WHERE
+ se.name = sed.parent
+ AND se.docstatus=1
+ AND se.purpose='Send to Subcontractor'
+ AND se.purchase_order = %s
+ AND sed.subcontracted_item = %s
+ AND sed.batch_no IS NOT NULL
+ GROUP BY
+ sed.batch_no,
+ sed.item_code
+ """, (purchase_order, fg_item), as_dict=1)
+
+ for batch_data in transferred_batches:
+ transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
+ transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
+
+ return transferred_batch_qty_map
+
+def get_backflushed_batch_qty_map(purchase_order, fg_item):
+ # returns
+ # {
+ # (item_code, fg_code): {
+ # batch1: 10, # qty
+ # batch2: 16
+ # },
+ # }
+ backflushed_batch_qty_map = {}
+ backflushed_batches = frappe.db.sql("""
+ SELECT
+ pris.batch_no,
+ SUM(pris.consumed_qty) AS qty,
+ pris.rm_item_code AS item_code
+ FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
+ WHERE
+ pr.name = pri.parent
+ AND pri.parent = pris.parent
+ AND pri.purchase_order = %s
+ AND pri.item_code = pris.main_item_code
+ AND pr.docstatus = 1
+ AND pris.main_item_code = %s
+ AND pris.batch_no IS NOT NULL
+ GROUP BY
+ pris.rm_item_code, pris.batch_no
+ """, (purchase_order, fg_item), as_dict=1)
+
+ for batch_data in backflushed_batches:
+ backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
+ backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
+
+ return backflushed_batch_qty_map
+
+def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
+ # Returns available batches to be backflushed based on requirements
+ transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
+ backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
+
+ available_batches = []
+
+ for (batch, transferred_qty) in transferred_batches.items():
+ backflushed_qty = backflushed_batches.get(batch, 0)
+ available_qty = transferred_qty - backflushed_qty
+
+ if available_qty >= required_qty:
+ available_batches.append({'batch': batch, 'qty': required_qty})
+ break
+ else:
+ available_batches.append({'batch': batch, 'qty': available_qty})
+ required_qty -= available_qty
+
+ return available_batches
\ No newline at end of file
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 2affba2..b6c4c47 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -171,7 +171,7 @@
self.save(ignore_permissions=True)
def _get_verify_url(self):
- verify_route = '/book-appointment/verify'
+ verify_route = '/book_appointment/verify'
params = {
'email': self.customer_email,
'appointment': self.name
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
index 3050d05..00a4bd1 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.py
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -41,7 +41,8 @@
email_campaign_exists = frappe.db.exists("Email Campaign", {
"campaign_name": self.campaign_name,
"recipient": self.recipient,
- "status": ("in", ["In Progress", "Scheduled"])
+ "status": ("in", ["In Progress", "Scheduled"]),
+ "name": ("!=", self.name)
})
if email_campaign_exists:
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
@@ -78,7 +79,7 @@
comm = make(
doctype = "Email Campaign",
name = email_campaign.name,
- subject = email_template.get("subject"),
+ subject = frappe.render_template(email_template.get("subject"), context),
content = frappe.render_template(email_template.get("response"), context),
sender = sender,
recipients = recipient,
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 99486fa..2880c80 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -130,10 +130,10 @@
def has_lost_quotation(self):
lost_quotation = frappe.db.sql("""
- select q.name
- from `tabQuotation` q, `tabQuotation Item` qi
- where q.name = qi.parent and q.docstatus=1
- and qi.prevdoc_docname =%s and q.status = 'Lost'
+ select name
+ from `tabQuotation`
+ where docstatus=1
+ and opportunity =%s and status = 'Lost'
""", self.name)
if lost_quotation:
if self.has_active_quotation():
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index d5348ff..7536172 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -71,7 +71,7 @@
def create_course_enrollments(self):
student = frappe.get_doc("Student", self.student)
program = frappe.get_doc("Program", self.program)
- course_list = [course.course for course in program.get_all_children()]
+ course_list = [course.course for course in program.courses]
for course_name in course_list:
student.enroll_in_course(course_name=course_name, program_enrollment=self.name)
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 9af5e22..8e4b4e1 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -5,12 +5,14 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe.utils import getdate,today
from frappe import _
from frappe.desk.form.linked_with import get_linked_doctypes
from erpnext.education.utils import check_content_completion, check_quiz_completion
class Student(Document):
def validate(self):
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
+ self.validate_dates()
if self.student_applicant:
self.check_unique()
@@ -19,6 +21,10 @@
if frappe.get_value("Student", self.name, "title") != self.title:
self.update_student_name_in_linked_doctype()
+ def validate_dates(self):
+ if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
+ frappe.throw(_("Date of Birth cannot be greater than today."))
+
def update_student_name_in_linked_doctype(self):
linked_doctypes = get_linked_doctypes("Student")
for d in linked_doctypes:
@@ -40,7 +46,7 @@
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
def after_insert(self):
- if not frappe.get_single('Education Settings').user_creation_skip:
+ if not frappe.get_single('Education Settings').get('user_creation_skip'):
self.create_student_user()
def create_student_user(self):
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py
index 141329b..7cec362 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py
@@ -63,10 +63,11 @@
item_code=%s""",(self.template, self.rate, self.item))
def create_item_from_template(doc):
+ disabled = 1
+
if(doc.is_billable == 1):
disabled = 0
- else:
- disabled = 1
+
#insert item
item = frappe.get_doc({
"doctype": "Item",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index c107cd7..835b38b 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import today, now_datetime
+from frappe.utils import today, now_datetime, getdate
from frappe.model.document import Document
from frappe.desk.reportview import get_match_cond
@@ -15,11 +15,20 @@
frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name)
def validate(self):
+ self.validate_dates()
self.validate_already_scheduled_or_admitted()
if self.status == "Discharged":
frappe.db.set_value("Patient", self.patient, "inpatient_status", None)
frappe.db.set_value("Patient", self.patient, "inpatient_record", None)
+ def validate_dates(self):
+ if (getdate(self.scheduled_date) < getdate(today())) or \
+ (getdate(self.admitted_datetime) < getdate(today())):
+ frappe.throw(_("Scheduled and Admitted dates can not be less than today"))
+ if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \
+ (getdate(self.discharge_date) < getdate(self.scheduled_date)):
+ frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date"))
+
def validate_already_scheduled_or_admitted(self):
query = """
select name, status
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index ed5bf9b..2a5e6d8 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -246,10 +246,10 @@
"on_trash": "erpnext.regional.check_deletion_permission"
},
'Address': {
- 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code']
+ 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
},
- ('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): {
- 'validate': 'erpnext.regional.india.utils.set_place_of_supply'
+ ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
+ 'validate': ['erpnext.regional.india.utils.set_place_of_supply']
},
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index bc4a1b4..7a9727f 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -5,9 +5,10 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import date_diff, add_days, getdate
+from frappe.utils import date_diff, add_days, getdate, cint
from frappe.model.document import Document
-from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, get_holidays_for_employee
+from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
+ get_holidays_for_employee, create_additional_leave_ledger_entry
class CompensatoryLeaveRequest(Document):
@@ -25,16 +26,14 @@
frappe.throw(_("Leave Type is madatory"))
def validate_attendance(self):
- query = """select attendance_date, status
- from `tabAttendance` where
- attendance_date between %(work_from_date)s and %(work_end_date)s
- and docstatus=1 and status = 'Present' and employee=%(employee)s"""
+ attendance = frappe.get_all('Attendance',
+ filters={
+ 'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
+ 'status': 'Present',
+ 'docstatus': 1,
+ 'employee': self.employee
+ }, fields=['attendance_date', 'status'])
- attendance = frappe.db.sql(query, {
- "work_from_date": self.work_from_date,
- "work_end_date": self.work_end_date,
- "employee": self.employee
- }, as_dict=True)
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
@@ -50,13 +49,19 @@
date_difference -= 0.5
leave_period = get_leave_period(self.work_from_date, self.work_end_date, company)
if leave_period:
- leave_allocation = self.exists_allocation_for_period(leave_period)
+ leave_allocation = self.get_existing_allocation_for_period(leave_period)
if leave_allocation:
leave_allocation.new_leaves_allocated += date_difference
- leave_allocation.submit()
+ leave_allocation.validate()
+ leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
+ leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
+
+ # generate additional ledger entry for the new compensatory leaves off
+ create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
+
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
- self.db_set("leave_allocation", leave_allocation.name)
+ self.leave_allocation=leave_allocation.name
else:
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
@@ -68,11 +73,16 @@
leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation)
if leave_allocation:
leave_allocation.new_leaves_allocated -= date_difference
- if leave_allocation.total_leaves_allocated - date_difference <= 0:
- leave_allocation.total_leaves_allocated = 0
- leave_allocation.submit()
+ if leave_allocation.new_leaves_allocated - date_difference <= 0:
+ leave_allocation.new_leaves_allocated = 0
+ leave_allocation.validate()
+ leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
+ leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
- def exists_allocation_for_period(self, leave_period):
+ # create reverse entry on cancelation
+ create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
+
+ def get_existing_allocation_for_period(self, leave_period):
leave_allocation = frappe.db.sql("""
select name
from `tabLeave Allocation`
@@ -95,17 +105,18 @@
def create_leave_allocation(self, leave_period, date_difference):
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
- allocation = frappe.new_doc("Leave Allocation")
- allocation.employee = self.employee
- allocation.employee_name = self.employee_name
- allocation.leave_type = self.leave_type
- allocation.from_date = add_days(self.work_end_date, 1)
- allocation.to_date = leave_period[0].to_date
- allocation.new_leaves_allocated = date_difference
- allocation.total_leaves_allocated = date_difference
- allocation.description = self.reason
- if is_carry_forward == 1:
- allocation.carry_forward = True
- allocation.save(ignore_permissions = True)
+ allocation = frappe.get_doc(dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ employee_name=self.employee_name,
+ leave_type=self.leave_type,
+ from_date=add_days(self.work_end_date, 1),
+ to_date=leave_period[0].to_date,
+ carry_forward=cint(is_carry_forward),
+ new_leaves_allocated=date_difference,
+ total_leaves_allocated=date_difference,
+ description=self.reason
+ ))
+ allocation.insert(ignore_permissions=True)
allocation.submit()
- return allocation
+ return allocation
\ No newline at end of file
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index f2ca1f4..1615ab3 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -5,37 +5,128 @@
import frappe
import unittest
+from frappe.utils import today, add_months, add_days
+from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee
+from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
+from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
-# class TestCompensatoryLeaveRequest(unittest.TestCase):
-# def get_compensatory_leave_request(self):
-# return frappe.get_doc('Compensatory Leave Request', dict(
-# employee = employee,
-# work_from_date = today,
-# work_to_date = today,
-# reason = 'test'
-# )).insert()
-#
-# def test_creation_of_leave_allocation(self):
-# employee = get_employee()
-# today = get_today()
-#
-# compensatory_leave_request = self.get_compensatory_leave_request(today)
-#
-# before = get_leave_balance(employee, compensatory_leave_request.leave_type)
-#
-# compensatory_leave_request.submit()
-#
-# self.assertEqual(get_leave_balance(employee, compensatory_leave_request.leave_type), before + 1)
-#
-# def test_max_compensatory_leave(self):
-# employee = get_employee()
-# today = get_today()
-#
-# compensatory_leave_request = self.get_compensatory_leave_request()
-#
-# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 0)
-#
-# self.assertRaises(MaxLeavesLimitCrossed, compensatory_leave_request.submit)
-#
-# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 10)
-#
+class TestCompensatoryLeaveRequest(unittest.TestCase):
+ def setUp(self):
+ frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
+ frappe.db.sql(''' delete from `tabLeave Ledger Entry`''')
+ frappe.db.sql(''' delete from `tabLeave Allocation`''')
+ frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec
+ create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
+ create_holiday_list()
+
+ employee = get_employee()
+ employee.holiday_list = "_Test Compensatory Leave"
+ employee.save()
+
+ def test_leave_balance_on_submit(self):
+ ''' check creation of leave allocation on submission of compensatory leave request '''
+ employee = get_employee()
+ mark_attendance(employee)
+ compensatory_leave_request = get_compensatory_leave_request(employee.name)
+
+ before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
+ compensatory_leave_request.submit()
+
+ self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1)
+
+ def test_leave_allocation_update_on_submit(self):
+ employee = get_employee()
+ mark_attendance(employee, date=add_days(today(), -1))
+ compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1))
+ compensatory_leave_request.submit()
+
+ # leave allocation creation on submit
+ leaves_allocated = frappe.db.get_value('Leave Allocation', {
+ 'name': compensatory_leave_request.leave_allocation
+ }, ['total_leaves_allocated'])
+ self.assertEqual(leaves_allocated, 1)
+
+ mark_attendance(employee)
+ compensatory_leave_request = get_compensatory_leave_request(employee.name)
+ compensatory_leave_request.submit()
+
+ # leave allocation updates on submission of second compensatory leave request
+ leaves_allocated = frappe.db.get_value('Leave Allocation', {
+ 'name': compensatory_leave_request.leave_allocation
+ }, ['total_leaves_allocated'])
+ self.assertEqual(leaves_allocated, 2)
+
+ def test_creation_of_leave_ledger_entry_on_submit(self):
+ ''' check creation of leave ledger entry on submission of leave request '''
+ employee = get_employee()
+ mark_attendance(employee)
+ compensatory_leave_request = get_compensatory_leave_request(employee.name)
+ compensatory_leave_request.submit()
+
+ filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
+ leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
+
+ self.assertEquals(len(leave_ledger_entry), 1)
+ self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEquals(leave_ledger_entry[0].leaves, 1)
+
+ # check reverse leave ledger entry on cancellation
+ compensatory_leave_request.cancel()
+ leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
+
+ self.assertEquals(len(leave_ledger_entry), 2)
+ self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEquals(leave_ledger_entry[0].leaves, -1)
+
+def get_compensatory_leave_request(employee, leave_date=today()):
+ prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
+ dict(leave_type='Compensatory Off',
+ work_from_date=leave_date,
+ work_end_date=leave_date,
+ employee=employee), 'name')
+ if prev_comp_leave_req:
+ return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req)
+
+ return frappe.get_doc(dict(
+ doctype='Compensatory Leave Request',
+ employee=employee,
+ leave_type='Compensatory Off',
+ work_from_date=leave_date,
+ work_end_date=leave_date,
+ reason='test'
+ )).insert()
+
+def mark_attendance(employee, date=today(), status='Present'):
+ if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')):
+ attendance = frappe.get_doc({
+ "doctype": "Attendance",
+ "employee": employee.name,
+ "attendance_date": date,
+ "status": status
+ })
+ attendance.save()
+ attendance.submit()
+
+def create_holiday_list():
+ if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
+ return
+
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "from_date": add_months(today(), -3),
+ "to_date": add_months(today(), 3),
+ "holidays": [
+ {
+ "description": "Test Holiday",
+ "holiday_date": today()
+ },
+ {
+ "description": "Test Holiday 1",
+ "holiday_date": add_days(today(), -1)
+ }
+ ],
+ "holiday_list_name": "_Test Compensatory Leave"
+ })
+ holiday_list.save()
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 703ec06..242531b 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime
+from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -152,8 +152,8 @@
elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)):
throw(_("Date Of Retirement must be greater than Date of Joining"))
- elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) <= getdate(self.date_of_joining)):
- throw(_("Relieving Date must be greater than Date of Joining"))
+ elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) < getdate(self.date_of_joining)):
+ throw(_("Relieving Date must be greater than or equal to Date of Joining"))
elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)):
throw(_("Contract End Date must be greater than Date of Joining"))
@@ -218,8 +218,8 @@
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
- cell_number = self.get('cell_number')
- prev_number = prev_doc.get('cell_number')
+ cell_number = cstr(self.get('cell_number'))
+ prev_number = cstr(prev_doc.get('cell_number'))
if (cell_number != prev_number or
self.get('user_id') != prev_doc.get('user_id')):
frappe.cache().hdel('employees_with_number', cell_number)
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 0d37c10..570f2ef 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -42,12 +42,6 @@
cur_frm.set_value("posting_date", frappe.datetime.get_today());
cur_frm.cscript.clear_sanctioned(doc);
}
-
- cur_frm.fields_dict.employee.get_query = function() {
- return {
- query: "erpnext.controllers.queries.employee_query"
- };
- };
};
cur_frm.cscript.clear_sanctioned = function(doc) {
@@ -119,7 +113,7 @@
};
erpnext.expense_claim = {
- set_title :function(frm) {
+ set_title: function(frm) {
if (!frm.doc.task) {
frm.set_value("title", frm.doc.employee_name);
}
@@ -131,20 +125,20 @@
frappe.ui.form.on("Expense Claim", {
setup: function(frm) {
- frm.trigger("set_query_for_cost_center");
- frm.trigger("set_query_for_payable_account");
frm.add_fetch("company", "cost_center", "cost_center");
frm.add_fetch("company", "default_expense_claim_payable_account", "payable_account");
- frm.set_query("employee_advance", "advances", function(doc) {
+
+ frm.set_query("employee_advance", "advances", function() {
return {
filters: [
['docstatus', '=', 1],
- ['employee', '=', doc.employee],
+ ['employee', '=', frm.doc.employee],
['paid_amount', '>', 0],
['paid_amount', '>', 'claimed_amount']
]
};
});
+
frm.set_query("expense_approver", function() {
return {
query: "erpnext.hr.doctype.department_approver.department_approver.get_approvers",
@@ -154,14 +148,49 @@
}
};
});
- frm.set_query("account_head", "taxes", function(doc) {
+
+ frm.set_query("account_head", "taxes", function() {
return {
filters: [
- ['company', '=', doc.company],
+ ['company', '=', frm.doc.company],
['account_type', 'in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"]]
]
};
});
+
+ frm.set_query("cost_center", "expenses", function() {
+ return {
+ filters: {
+ "company": frm.doc.company,
+ "is_group": 0
+ }
+ };
+ });
+
+ frm.set_query("payable_account", function() {
+ return {
+ filters: {
+ "report_type": "Balance Sheet",
+ "account_type": "Payable",
+ "company": frm.doc.company,
+ "is_group": 0
+ }
+ };
+ });
+
+ frm.set_query("task", function() {
+ return {
+ filters: {
+ 'project': frm.doc.project
+ }
+ };
+ });
+
+ frm.set_query("employee", function() {
+ return {
+ query: "erpnext.controllers.queries.employee_query"
+ };
+ });
},
onload: function(frm) {
@@ -244,30 +273,6 @@
});
},
- set_query_for_cost_center: function(frm) {
- frm.fields_dict["cost_center"].get_query = function() {
- return {
- filters: {
- "company": frm.doc.company,
- "is_group": 0
- }
- };
- };
- },
-
- set_query_for_payable_account: function(frm) {
- frm.fields_dict["payable_account"].get_query = function() {
- return {
- filters: {
- "report_type": "Balance Sheet",
- "account_type": "Payable",
- "company": frm.doc.company,
- "is_group": 0
- }
- };
- };
- },
-
is_paid: function(frm) {
frm.trigger("toggle_fields");
},
@@ -329,6 +334,10 @@
});
frappe.ui.form.on("Expense Claim Detail", {
+ expenses_add: function(frm, cdt, cdn) {
+ var row = frappe.get_doc(cdt, cdn);
+ frm.script_manager.copy_from_first_row("expenses", row, ["cost_center"]);
+ },
amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
var doc = frm.doc;
@@ -341,6 +350,9 @@
cur_frm.cscript.calculate_total(doc,cdt,cdn);
frm.trigger("get_taxes");
frm.trigger("calculate_grand_total");
+ },
+ cost_center: function(frm, cdt, cdn) {
+ erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "expenses", "cost_center");
}
});
@@ -411,12 +423,4 @@
tax_amount: function(frm, cdt, cdn) {
frm.trigger("calculate_total_tax", cdt, cdn);
}
-});
-
-cur_frm.fields_dict['task'].get_query = function(doc) {
- return {
- filters:{
- 'project': doc.project
- }
- };
-};
+});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 5c2f490..b5b6823 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -43,7 +43,6 @@
"accounting_dimensions_section",
"project",
"dimension_col_break",
- "cost_center",
"more_details",
"status",
"amended_from",
@@ -366,7 +365,7 @@
"icon": "fa fa-money",
"idx": 1,
"is_submittable": 1,
- "modified": "2019-11-08 14:13:08.964547",
+ "modified": "2019-11-09 14:13:08.964547",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 5939150..dfb0bb9 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -127,7 +127,7 @@
"debit": data.sanctioned_amount,
"debit_in_account_currency": data.sanctioned_amount,
"against": self.employee,
- "cost_center": self.cost_center
+ "cost_center": data.cost_center
})
)
@@ -190,8 +190,9 @@
)
def validate_account_details(self):
- if not self.cost_center:
- frappe.throw(_("Cost center is required to book an expense claim"))
+ for data in self.expenses:
+ if not data.cost_center:
+ frappe.throw(_("Cost center is required to book an expense claim"))
if self.is_paid:
if not self.mode_of_payment:
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index b559dfd..6e97f05 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -126,7 +126,7 @@
def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
employee = frappe.db.get_value("Employee", {"status": "Active"})
- currency = frappe.db.get_value('Company', company, 'default_currency')
+ currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
expense_claim = {
"doctype": "Expense Claim",
"employee": employee,
@@ -134,12 +134,15 @@
"approval_status": "Approved",
"company": company,
'currency': currency,
- "expenses":
- [{"expense_type": "Travel",
+ "expenses": [{
+ "expense_type": "Travel",
"default_account": account,
- 'currency': currency,
+ "currency": currency,
"amount": amount,
- "sanctioned_amount": sanctioned_amount}]}
+ "sanctioned_amount": sanctioned_amount,
+ "cost_center": cost_center
+ }]
+ }
if taxes:
expense_claim.update(taxes)
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index b23fb6a..b60db2c 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -1,378 +1,118 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
"creation": "2013-02-22 01:27:46",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "expense_date",
+ "column_break_2",
+ "expense_type",
+ "default_account",
+ "section_break_4",
+ "description",
+ "section_break_6",
+ "amount",
+ "column_break_8",
+ "sanctioned_amount",
+ "cost_center"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "expense_date",
"fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Expense Date",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "expense_date",
"oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
"width": "150px"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "expense_type",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Expense Claim Type",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "expense_type",
"oldfieldtype": "Link",
"options": "Expense Claim Type",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
"width": "150px"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"depends_on": "expense_type",
"fieldname": "default_account",
"fieldtype": "Link",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Default Account",
- "length": 0,
- "no_copy": 0,
"options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "",
"fieldname": "description",
"fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Description",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Small Text",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"print_width": "300px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
"width": "300px"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Amount",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "claim_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
"width": "150px"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_8",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "sanctioned_amount",
"fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Sanctioned Amount",
- "length": 0,
"no_copy": 1,
"oldfieldname": "sanctioned_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
"width": "150px"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
"idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
"istable": 1,
- "max_attachments": 0,
- "modified": "2019-06-10 08:41:36.122565",
- "modified_by": "Administrator",
+ "modified": "2019-11-22 11:57:25.110942",
+ "modified_by": "jangeles@bai.ph",
"module": "HR",
"name": "Expense Claim Detail",
"owner": "harshada@webnotestech.com",
"permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js
index fda6809..c487797 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js
@@ -8,7 +8,7 @@
return{
filters: {
"is_group": 0,
- "root_type": "Expense",
+ "root_type": frm.doc.deferred_expense_account ? "Asset" : "Expense",
'company': d.company
}
}
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
index d0c4122..e45f640 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
@@ -1,181 +1,72 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:expense_type",
- "beta": 0,
- "creation": "2012-03-27 14:35:55",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:expense_type",
+ "creation": "2012-03-27 14:35:55",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "deferred_expense_account",
+ "expense_type",
+ "description",
+ "accounts"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_type",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Expense Claim Type",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "expense_type",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "expense_type",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Expense Claim Type",
+ "oldfieldname": "expense_type",
+ "oldfieldtype": "Data",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Small Text",
"width": "300px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Accounts",
- "length": 0,
- "no_copy": 0,
- "options": "Expense Claim Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "label": "Accounts",
+ "options": "Expense Claim Account"
+ },
+ {
+ "default": "0",
+ "fieldname": "deferred_expense_account",
+ "fieldtype": "Check",
+ "label": "Deferred Expense Account"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-flag",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 14:13:43.770829",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Expense Claim Type",
- "owner": "harshada@webnotestech.com",
+ ],
+ "icon": "fa fa-flag",
+ "idx": 1,
+ "modified": "2019-11-22 12:00:18.710408",
+ "modified_by": "jangeles@bai.ph",
+ "module": "HR",
+ "name": "Expense Claim Type",
+ "owner": "harshada@webnotestech.com",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Employee",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "Employee"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 874ae7a..d13bb45 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -69,10 +69,14 @@
def validate_allocation_overlap(self):
leave_allocation = frappe.db.sql("""
- select name from `tabLeave Allocation`
- where employee=%s and leave_type=%s and docstatus=1
- and to_date >= %s and from_date <= %s""",
- (self.employee, self.leave_type, self.from_date, self.to_date))
+ SELECT
+ name
+ FROM `tabLeave Allocation`
+ WHERE
+ employee=%s AND leave_type=%s
+ AND name <> %s AND docstatus=1
+ AND to_date >= %s AND from_date <= %s""",
+ (self.employee, self.leave_type, self.name, self.from_date, self.to_date))
if leave_allocation:
frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index db3819e..14ffa0e 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -60,6 +60,7 @@
}
}
});
+ $("div").remove(".form-dashboard-section.custom");
frm.dashboard.add_section(
frappe.render_template('leave_application_dashboard', {
data: leave_details
@@ -170,7 +171,7 @@
frm.set_value('to_date', '');
return;
}
- // server call is done to include holidays in leave days calculations
+ // server call is done to include holidays in leave days calculations
return frappe.call({
method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
args: {
@@ -193,7 +194,7 @@
set_leave_approver: function(frm) {
if(frm.doc.employee) {
- // server call is done to include holidays in leave days calculations
+ // server call is done to include holidays in leave days calculations
return frappe.call({
method: 'erpnext.hr.doctype.leave_application.leave_application.get_leave_approver',
args: {
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 0e66305..915cea1 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -351,7 +351,7 @@
pass
def create_leave_ledger_entry(self, submit=True):
- if self.status != 'Approved':
+ if self.status != 'Approved' and submit:
return
expiry_date = get_allocation_expiry(self.employee, self.leave_type,
@@ -549,10 +549,10 @@
leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
- and not skip_expiry_leaves(leave_entry, to_date):
+ and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
leave_days += leave_entry.leaves
- else:
+ elif leave_entry.transaction_type == 'Leave Application':
if leave_entry.from_date < getdate(from_date):
leave_entry.from_date = from_date
if leave_entry.to_date > getdate(to_date):
@@ -579,14 +579,15 @@
def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date '''
return frappe.db.sql("""
- select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward, transaction_name
- from `tabLeave Ledger Entry`
- where employee=%(employee)s and leave_type=%(leave_type)s
- and docstatus=1
- and leaves<0
- and (from_date between %(from_date)s and %(to_date)s
- or to_date between %(from_date)s and %(to_date)s
- or (from_date < %(from_date)s and to_date > %(to_date)s))
+ SELECT
+ employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type,
+ is_carry_forward, is_expired
+ FROM `tabLeave Ledger Entry`
+ WHERE employee=%(employee)s AND leave_type=%(leave_type)s
+ AND docstatus=1 AND leaves<0
+ AND (from_date between %(from_date)s AND %(to_date)s
+ OR to_date between %(from_date)s AND %(to_date)s
+ OR (from_date < %(from_date)s AND to_date > %(to_date)s))
""", {
"from_date": from_date,
"to_date": to_date,
@@ -773,4 +774,4 @@
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
- return leave_approver
+ return leave_approver
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 38ae808..b9c0210 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -301,7 +301,7 @@
to_date = add_days(date, 2),
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
leave_application.submit()
@@ -314,7 +314,7 @@
to_date = add_days(date, 8),
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
self.assertRaises(frappe.ValidationError, leave_application.insert)
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 850a08d..1762cf9 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -43,10 +43,18 @@
leave_period.grant_leave_allocation(employee=employee_doc_name)
self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
-def create_leave_period(from_date, to_date):
+def create_leave_period(from_date, to_date, company=None):
+ leave_period = frappe.db.get_value('Leave Period',
+ dict(company=company or erpnext.get_default_company(),
+ from_date=from_date,
+ to_date=to_date,
+ is_active=1), 'name')
+ if leave_period:
+ return frappe.get_doc("Leave Period", leave_period)
+
leave_period = frappe.get_doc({
"doctype": "Leave Period",
- "company": erpnext.get_default_company(),
+ "company": company or erpnext.get_default_company(),
"from_date": from_date,
"to_date": to_date,
"is_active": 1
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js
index adc0671..d25eb6d 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js
@@ -31,7 +31,11 @@
}
if ((frm.doc.employees || []).length) {
frm.page.set_primary_action(__('Create Salary Slips'), () => {
- frm.save('Submit');
+ frm.save('Submit').then(()=>{
+ frm.page.clear_primary_action();
+ frm.refresh();
+ frm.events.refresh(frm);
+ });
});
}
}
diff --git a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json
index a116185..5bb2d37 100644
--- a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json
+++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json
@@ -1,231 +1,82 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-12-20 15:32:25.078334",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "creation": "2016-12-20 15:32:25.078334",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "payment_date",
+ "principal_amount",
+ "interest_amount",
+ "total_payment",
+ "balance_loan_amount",
+ "paid"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "payment_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Payment Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "columns": 2,
+ "fieldname": "payment_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Payment Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "principal_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Principal Amount",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "principal_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Principal Amount",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "interest_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Interest Amount",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "interest_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Interest Amount",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "total_payment",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Total Payment",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "total_payment",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Total Payment",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "balance_loan_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Balance Loan Amount",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "balance_loan_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Balance Loan Amount",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "paid",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Paid",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "paid",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Paid",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-03-30 17:37:31.834792",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Repayment Schedule",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "modified": "2019-10-29 11:45:10.694557",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Repayment Schedule",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 1464a77..c3e8d27 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -321,11 +321,11 @@
if new_allocation == allocation.total_leaves_allocated:
continue
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
- create_earned_leave_ledger_entry(allocation, earned_leaves, today)
+ create_additional_leave_ledger_entry(allocation, earned_leaves, today)
-def create_earned_leave_ledger_entry(allocation, earned_leaves, date):
- ''' Create leave ledger entry based on the earned leave frequency '''
- allocation.new_leaves_allocated = earned_leaves
+def create_additional_leave_ledger_entry(allocation, leaves, date):
+ ''' Create leave ledger entry for leave types '''
+ allocation.new_leaves_allocated = leaves
allocation.from_date = date
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
@@ -389,6 +389,7 @@
def get_holidays_for_employee(employee, start_date, end_date):
holiday_list = get_holiday_list_for_employee(employee)
+
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday`
where
parent=%(holiday_list)s
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index faed707..38118bd 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -44,6 +44,8 @@
target.item_name = item.get("item_name")
target.description = item.get("description")
target.uom = item.get("stock_uom")
+ target.against_blanket_order = 1
+ target.blanket_order = source_name
target_doc = get_mapped_doc("Blanket Order", source_name, {
"Blanket Order": {
@@ -71,6 +73,8 @@
target.description = item.get("description")
target.uom = item.get("stock_uom")
target.warehouse = item.get("default_warehouse")
+ target.against_blanket_order = 1
+ target.blanket_order = source_name
target_doc = get_mapped_doc("Blanket Order", source_name, {
"Blanket Order": {
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 8283fd7..3acaee4 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -6,7 +6,6 @@
frappe.ui.form.on("BOM", {
setup: function(frm) {
frm.custom_make_buttons = {
- 'BOM': 'Duplicate BOM',
'Work Order': 'Work Order',
'Quality Inspection': 'Quality Inspection'
};
@@ -91,10 +90,6 @@
}
if(frm.doc.docstatus!=0) {
- frm.add_custom_button(__("Duplicate BOM"), function() {
- frm.copy_doc();
- }, __("Create"));
-
frm.add_custom_button(__("Work Order"), function() {
frm.trigger("make_work_order");
}, __("Create"));
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 5579954..e3ece56 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -606,6 +606,7 @@
item.image,
bom.project,
item.stock_uom,
+ item.item_group,
item.allow_alternative_item,
item_default.default_warehouse,
item_default.expense_account as expense_account,
diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
index 060cd53..361826e 100644
--- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py
+++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py
@@ -25,5 +25,5 @@
}
],
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
- "Purchase Invoice", "Job Card", "Stock Entry"]
+ "Purchase Invoice", "Job Card", "Stock Entry", "BOM"]
}
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 2ca4d16..31a9fdb 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -9,6 +9,7 @@
from six import string_types
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
from frappe.model.document import Document
+import click
class BOMUpdateTool(Document):
def replace_bom(self):
@@ -17,7 +18,8 @@
frappe.cache().delete_key('bom_children')
bom_list = self.get_parent_boms(self.new_bom)
updated_bom = []
-
+ with click.progressbar(bom_list) as bom_list:
+ pass
for bom in bom_list:
try:
bom_obj = frappe.get_cached_doc('BOM', bom)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 95549d5..bc8c229 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -3,6 +3,9 @@
frappe.ui.form.on('Job Card', {
refresh: function(frm) {
+ frappe.flags.pause_job = 0;
+ frappe.flags.resume_job = 0;
+
if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) {
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
frm.add_custom_button(__("Material Request"), () => {
@@ -13,44 +16,99 @@
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
frm.add_custom_button(__("Material Transfer"), () => {
frm.trigger("make_stock_entry");
- });
+ }).addClass("btn-primary");
}
}
- if (frm.doc.docstatus == 0) {
- frm.trigger("make_dashboard");
+ if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty
+ && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
+ frm.trigger("prepare_timer_buttons");
+ }
+ },
- if (!frm.doc.job_started) {
- frm.add_custom_button(__("Start Job"), () => {
- let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
- row.from_time = frappe.datetime.now_datetime();
- frm.set_value('job_started', 1);
- frm.set_value('started_time' , row.from_time);
- frm.save();
- });
- } else {
- frm.add_custom_button(__("Complete Job"), () => {
- let completed_time = frappe.datetime.now_datetime();
- frm.doc.time_logs.forEach(d => {
- if (d.from_time && !d.to_time) {
- d.to_time = completed_time;
- frm.set_value('started_time' , '');
- frm.set_value('job_started', 0);
- frm.save();
+ prepare_timer_buttons: function(frm) {
+ frm.trigger("make_dashboard");
+ if (!frm.doc.job_started) {
+ frm.add_custom_button(__("Start"), () => {
+ if (!frm.doc.employee) {
+ frappe.prompt({fieldtype: 'Link', label: __('Employee'), options: "Employee",
+ fieldname: 'employee'}, d => {
+ if (d.employee) {
+ frm.set_value("employee", d.employee);
}
- })
- });
- }
+
+ frm.events.start_job(frm);
+ }, __("Enter Value"), __("Start"));
+ } else {
+ frm.events.start_job(frm);
+ }
+ }).addClass("btn-primary");
+ } else if (frm.doc.status == "On Hold") {
+ frm.add_custom_button(__("Resume"), () => {
+ frappe.flags.resume_job = 1;
+ frm.events.start_job(frm);
+ }).addClass("btn-primary");
+ } else {
+ frm.add_custom_button(__("Pause"), () => {
+ frappe.flags.pause_job = 1;
+ frm.set_value("status", "On Hold");
+ frm.events.complete_job(frm);
+ });
+
+ frm.add_custom_button(__("Complete"), () => {
+ let completed_time = frappe.datetime.now_datetime();
+ frm.trigger("hide_timer");
+
+ frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
+ fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
+ frm.events.complete_job(frm, completed_time, data.qty);
+ }, __("Enter Value"), __("Complete"));
+ }).addClass("btn-primary");
}
},
+ start_job: function(frm) {
+ let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
+ row.from_time = frappe.datetime.now_datetime();
+ frm.set_value('job_started', 1);
+ frm.set_value('started_time' , row.from_time);
+ frm.set_value("status", "Work In Progress");
+
+ if (!frappe.flags.resume_job) {
+ frm.set_value('current_time' , 0);
+ }
+
+ frm.save();
+ },
+
+ complete_job: function(frm, completed_time, completed_qty) {
+ frm.doc.time_logs.forEach(d => {
+ if (d.from_time && !d.to_time) {
+ d.to_time = completed_time || frappe.datetime.now_datetime();
+ d.completed_qty = completed_qty || 0;
+
+ if(frappe.flags.pause_job) {
+ let currentIncrement = moment(d.to_time).diff(moment(d.from_time),"seconds") || 0;
+ frm.set_value('current_time' , currentIncrement + (frm.doc.current_time || 0));
+ } else {
+ frm.set_value('started_time' , '');
+ frm.set_value('job_started', 0);
+ frm.set_value('current_time' , 0);
+ }
+
+ frm.save();
+ }
+ });
+ },
+
make_dashboard: function(frm) {
if(frm.doc.__islocal)
return;
frm.dashboard.refresh();
const timer = `
- <div class="stopwatch" style="font-weight:bold">
+ <div class="stopwatch" style="font-weight:bold;margin:0px 13px 0px 2px;
+ color:#545454;font-size:18px;display:inline-block;vertical-align:text-bottom;>
<span class="hours">00</span>
<span class="colon">:</span>
<span class="minutes">00</span>
@@ -58,11 +116,16 @@
<span class="seconds">00</span>
</div>`;
- var section = frm.dashboard.add_section(timer);
+ var section = frm.toolbar.page.add_inner_message(timer);
- if (frm.doc.started_time) {
- let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds");
- initialiseTimer();
+ let currentIncrement = frm.doc.current_time || 0;
+ if (frm.doc.started_time || frm.doc.current_time) {
+ if (frm.doc.status == "On Hold") {
+ updateStopwatch(currentIncrement);
+ } else {
+ currentIncrement += moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds");
+ initialiseTimer();
+ }
function initialiseTimer() {
const interval = setInterval(function() {
@@ -70,12 +133,12 @@
updateStopwatch(current);
}, 1000);
}
-
+
function updateStopwatch(increment) {
var hours = Math.floor(increment / 3600);
var minutes = Math.floor((increment - (hours * 3600)) / 60);
var seconds = increment - (hours * 3600) - (minutes * 60);
-
+
$(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
$(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
$(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
@@ -88,6 +151,10 @@
}
},
+ hide_timer: function(frm) {
+ frm.toolbar.page.inner_toolbar.find(".stopwatch").remove();
+ },
+
for_quantity: function(frm) {
frm.doc.items = [];
frm.call({
@@ -117,5 +184,22 @@
timer: function(frm) {
return `<button> Start </button>`
+ },
+
+ set_total_completed_qty: function(frm) {
+ frm.doc.total_completed_qty = 0;
+ frm.doc.time_logs.forEach(d => {
+ if (d.completed_qty) {
+ frm.doc.total_completed_qty += d.completed_qty;
+ }
+ });
+
+ refresh_field("total_completed_qty");
}
-});
\ No newline at end of file
+});
+
+frappe.ui.form.on('Job Card Time Log', {
+ completed_qty: function(frm) {
+ frm.events.set_total_completed_qty(frm);
+ }
+})
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 39c5cce..156acce 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -1,1071 +1,350 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "PO-JOB.#####",
- "beta": 0,
- "creation": "2018-07-09 17:23:29.518745",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "naming_series:",
+ "creation": "2018-07-09 17:23:29.518745",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "work_order",
+ "bom_no",
+ "workstation",
+ "operation",
+ "column_break_4",
+ "posting_date",
+ "company",
+ "remarks",
+ "production_section",
+ "production_item",
+ "item_name",
+ "for_quantity",
+ "wip_warehouse",
+ "column_break_12",
+ "employee",
+ "employee_name",
+ "status",
+ "project",
+ "timing_detail",
+ "time_logs",
+ "section_break_13",
+ "total_completed_qty",
+ "total_time_in_mins",
+ "column_break_15",
+ "section_break_8",
+ "items",
+ "more_information",
+ "operation_id",
+ "transferred_qty",
+ "requested_qty",
+ "column_break_20",
+ "barcode",
+ "job_started",
+ "started_time",
+ "current_time",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "work_order",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Work Order",
- "length": 0,
- "no_copy": 0,
- "options": "Work Order",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Work Order",
+ "options": "Work Order",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "BOM No",
- "length": 0,
- "no_copy": 0,
- "options": "BOM",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "work_order.bom_no",
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "options": "BOM",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "workstation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Workstation",
- "length": 0,
- "no_copy": 0,
- "options": "Workstation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Workstation",
+ "options": "Workstation",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "operation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Operation",
- "length": 0,
- "no_copy": 0,
- "options": "Operation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Operation",
+ "options": "Operation",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fetch_if_empty": 0,
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Posting Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "for_quantity",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "For Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "for_quantity",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Qty To Manufacture",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "wip_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "WIP Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "wip_warehouse",
+ "fieldtype": "Link",
+ "label": "WIP Warehouse",
+ "options": "Warehouse",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "timing_detail",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Timing Detail",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "timing_detail",
+ "fieldtype": "Section Break",
+ "label": "Timing Detail"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Employee",
+ "options": "Employee"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "time_logs",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Time Logs",
- "length": 0,
- "no_copy": 0,
- "options": "Job Card Time Log",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "time_logs",
+ "fieldtype": "Table",
+ "label": "Time Logs",
+ "options": "Job Card Time Log"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "section_break_13",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_13",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "total_completed_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Completed Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "label": "Total Completed Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_15",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_15",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "total_time_in_mins",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Time in Mins",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "label": "Total Time in Mins",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Raw Materials",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Raw Materials"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "options": "Job Card Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "options": "Job Card Item",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "more_information",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "More Information",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "fieldname": "more_information",
+ "fieldtype": "Section Break",
+ "label": "More Information"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "operation_id",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Operation ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "operation_id",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Operation ID",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "transferred_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Transferred Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "label": "Transferred Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "requested_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Requested Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "requested_qty",
+ "fieldtype": "Float",
+ "label": "Requested Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "project",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "remarks",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Remarks",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "remarks",
+ "fieldtype": "Small Text",
+ "label": "Remarks"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_20",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_20",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Open",
- "fetch_if_empty": 0,
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 1,
- "options": "Open\nWork In Progress\nMaterial Transferred\nSubmitted\nCancelled\nCompleted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Open",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Open\nWork In Progress\nMaterial Transferred\nOn Hold\nSubmitted\nCancelled\nCompleted",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "job_started",
- "fieldtype": "Check",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Job Started",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "job_started",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Job Started",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "started_time",
- "fieldtype": "Datetime",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Started Time",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "started_time",
+ "fieldtype": "Datetime",
+ "hidden": 1,
+ "label": "Started Time",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Job Card",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Job Card",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "PO-JOB.#####",
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "PO-JOB.#####",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Read Only",
+ "label": "Employee Name"
+ },
+ {
+ "fieldname": "production_section",
+ "fieldtype": "Section Break",
+ "label": "Production"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "work_order.production_item",
+ "fieldname": "production_item",
+ "fieldtype": "Read Only",
+ "label": "Production Item"
+ },
+ {
+ "fieldname": "barcode",
+ "fieldtype": "Barcode",
+ "label": "Barcode",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "work_order.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
+ {
+ "fieldname": "current_time",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "Current Time",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-03-10 17:38:37.499871",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Job Card",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "modified": "2019-12-03 13:08:57.926201",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "operation",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "operation",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 9d2e620..9a2aaa5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -4,10 +4,16 @@
from __future__ import unicode_literals
import frappe
+import datetime
from frappe import _
-from frappe.utils import flt, time_diff_in_hours, get_datetime
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
+from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
+ get_time, add_to_date, time_diff, add_days, get_datetime_str)
+
+from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
+
+class OverlapError(frappe.ValidationError): pass
class JobCard(Document):
def validate(self):
@@ -26,7 +32,7 @@
data = self.get_overlap_for(d)
if data:
frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
- .format(d.idx, self.name, data.name))
+ .format(d.idx, self.name, data.name), OverlapError)
if d.from_time and d.to_time:
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
@@ -35,27 +41,120 @@
if d.completed_qty:
self.total_completed_qty += d.completed_qty
- def get_overlap_for(self, args):
- existing = frappe.db.sql("""select jc.name as name from
+ def get_overlap_for(self, args, check_next_available_slot=False):
+ production_capacity = 1
+
+ if self.workstation:
+ production_capacity = frappe.get_cached_value("Workstation",
+ self.workstation, 'production_capacity') or 1
+ validate_overlap_for = " and jc.workstation = %(workstation)s "
+
+ if self.employee:
+ # override capacity for employee
+ production_capacity = 1
+ validate_overlap_for = " and jc.employee = %(employee)s "
+
+ extra_cond = ''
+ if check_next_available_slot:
+ extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)"
+
+ existing = frappe.db.sql("""select jc.name as name, jctl.to_time from
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
(
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
(%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or
- (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time))
- and jctl.name!=%(name)s
- and jc.name!=%(parent)s
- and jc.docstatus < 2
- and jc.employee = %(employee)s """,
+ (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
+ )
+ and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
+ order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for),
{
"from_time": args.from_time,
"to_time": args.to_time,
"name": args.name or "No Name",
"parent": args.parent or "No Name",
- "employee": self.employee
+ "employee": self.employee,
+ "workstation": self.workstation
}, as_dict=True)
+ if existing and production_capacity > len(existing):
+ return
+
return existing[0] if existing else None
+ def schedule_time_logs(self, row):
+ row.remaining_time_in_mins = row.time_in_mins
+ while row.remaining_time_in_mins > 0:
+ args = frappe._dict({
+ "from_time": row.planned_start_time,
+ "to_time": row.planned_end_time
+ })
+
+ self.validate_overlap_for_workstation(args, row)
+ self.check_workstation_time(row)
+
+ def validate_overlap_for_workstation(self, args, row):
+ # get the last record based on the to time from the job card
+ data = self.get_overlap_for(args, check_next_available_slot=True)
+ if data:
+ row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
+
+ def check_workstation_time(self, row):
+ workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
+ if (not workstation_doc.working_hours or
+ cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))):
+ row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time,
+ row.planned_start_time)
+
+ self.update_time_logs(row)
+ return
+
+ start_date = getdate(row.planned_start_time)
+ start_time = get_time(row.planned_start_time)
+
+ new_start_date = workstation_doc.validate_workstation_holiday(start_date)
+
+ if new_start_date != start_date:
+ row.planned_start_time = datetime.datetime.combine(new_start_date, start_time)
+ start_date = new_start_date
+
+ total_idx = len(workstation_doc.working_hours)
+
+ for i, time_slot in enumerate(workstation_doc.working_hours):
+ workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time))
+ workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time))
+
+ if (get_datetime(row.planned_start_time) >= workstation_start_time and
+ get_datetime(row.planned_start_time) <= workstation_end_time):
+ time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time)
+
+ # If remaining time fit in workstation time logs else split hours as per workstation time
+ if time_in_mins > row.remaining_time_in_mins:
+ row.planned_end_time = add_to_date(row.planned_start_time,
+ minutes=row.remaining_time_in_mins)
+ row.remaining_time_in_mins = 0
+ else:
+ row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins)
+ row.remaining_time_in_mins -= time_in_mins
+
+ self.update_time_logs(row)
+
+ if total_idx != (i+1) and row.remaining_time_in_mins > 0:
+ row.planned_start_time = datetime.datetime.combine(start_date,
+ get_time(workstation_doc.working_hours[i+1].start_time))
+
+ if row.remaining_time_in_mins > 0:
+ start_date = add_days(start_date, 1)
+ row.planned_start_time = datetime.datetime.combine(start_date,
+ get_time(workstation_doc.working_hours[0].start_time))
+
+ def update_time_logs(self, row):
+ self.append("time_logs", {
+ "from_time": row.planned_start_time,
+ "to_time": row.planned_end_time,
+ "completed_qty": 0,
+ "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
+ })
+
def get_required_items(self):
if not self.get('work_order'):
return
@@ -95,8 +194,9 @@
if self.total_completed_qty <= 0.0:
frappe.throw(_("Total completed qty must be greater than zero"))
- if self.total_completed_qty > self.for_quantity:
- frappe.throw(_("Total completed qty can not be greater than for quantity"))
+ if self.total_completed_qty != self.for_quantity:
+ frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
+ .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
def update_work_order(self):
if not self.work_order:
@@ -172,6 +272,8 @@
self.set_status(update_status)
def set_status(self, update_status=False):
+ if self.status == "On Hold": return
+
self.status = {
0: "Open",
1: "Submitted",
@@ -230,6 +332,7 @@
target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
target.calculate_rate_and_amount()
target.set_missing_values()
+ target.set_stock_entry_type()
doclist = get_mapped_doc("Job Card", source_name, {
"Job Card": {
@@ -251,3 +354,48 @@
}, target_doc, set_missing_values)
return doclist
+
+def time_diff_in_minutes(string_ed_date, string_st_date):
+ return time_diff(string_ed_date, string_st_date).total_seconds() / 60
+
+@frappe.whitelist()
+def get_job_details(start, end, filters=None):
+ events = []
+
+ event_color = {
+ "Completed": "#cdf5a6",
+ "Material Transferred": "#ffdd9e",
+ "Work In Progress": "#D3D3D3"
+ }
+
+ from frappe.desk.reportview import get_filters_cond
+ conditions = get_filters_cond("Job Card", filters, [])
+
+ job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
+ `tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
+ min(`tabJob Card Time Log`.from_time) as from_time,
+ max(`tabJob Card Time Log`.to_time) as to_time
+ FROM `tabJob Card` , `tabJob Card Time Log`
+ WHERE
+ `tabJob Card`.name = `tabJob Card Time Log`.parent {0}
+ group by `tabJob Card`.name""".format(conditions), as_dict=1)
+
+ for d in job_cards:
+ subject_data = []
+ for field in ["name", "work_order", "remarks", "employee_name"]:
+ if not d.get(field): continue
+
+ subject_data.append(d.get(field))
+
+ color = event_color.get(d.status)
+ job_card_data = {
+ 'from_time': d.from_time,
+ 'to_time': d.to_time,
+ 'name': d.name,
+ 'subject': '\n'.join(subject_data),
+ 'color': color if color else "#89bcde"
+ }
+
+ events.append(job_card_data)
+
+ return events
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
new file mode 100644
index 0000000..cf07698
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
@@ -0,0 +1,21 @@
+frappe.views.calendar["Job Card"] = {
+ field_map: {
+ "start": "from_time",
+ "end": "to_time",
+ "id": "name",
+ "title": "subject",
+ "color": "color",
+ "allDay": "allDay",
+ "progress": "progress"
+ },
+ gantt: true,
+ filters: [
+ {
+ "fieldtype": "Link",
+ "fieldname": "employee",
+ "options": "Employee",
+ "label": __("Employee")
+ }
+ ],
+ get_events_method: "erpnext.manufacturing.doctype.job_card.job_card.get_job_details"
+};
diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
index 2aab71d..9dd54dd 100644
--- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
+++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
@@ -1,208 +1,57 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2019-03-08 23:56:43.187569",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "creation": "2019-03-08 23:56:43.187569",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "from_time",
+ "to_time",
+ "column_break_2",
+ "time_in_mins",
+ "completed_qty"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "From Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "From Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "To Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "To Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "time_in_mins",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Time In Mins",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "time_in_mins",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Time In Mins",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "completed_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Completed Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "completed_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Completed Qty",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-03-10 17:08:46.504910",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Job Card Time Log",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "modified": "2019-12-03 12:56:02.285448",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Time Log",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index 461b9ab..86fa7a8 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -1,585 +1,178 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-11-27 14:12:07.542534",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "creation": "2014-11-27 14:12:07.542534",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "raw_materials_consumption_section",
+ "material_consumption",
+ "column_break_3",
+ "backflush_raw_materials_based_on",
+ "capacity_planning",
+ "disable_capacity_planning",
+ "allow_overtime",
+ "allow_production_on_holidays",
+ "column_break_5",
+ "capacity_planning_for_days",
+ "mins_between_operations",
+ "section_break_6",
+ "default_wip_warehouse",
+ "default_fg_warehouse",
+ "column_break_11",
+ "default_scrap_warehouse",
+ "over_production_for_sales_and_work_order_section",
+ "overproduction_percentage_for_sales_order",
+ "column_break_16",
+ "overproduction_percentage_for_work_order",
+ "other_settings_section",
+ "update_bom_costs_automatically"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "capacity_planning",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Capacity Planning",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "capacity_planning",
+ "fieldtype": "Section Break",
+ "label": "Capacity Planning"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order",
- "fieldname": "disable_capacity_planning",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Disable Capacity Planning and Time Tracking",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "depends_on": "eval:!doc.disable_capacity_planning",
+ "description": "Plan time logs outside Workstation Working Hours.",
+ "fieldname": "allow_overtime",
+ "fieldtype": "Check",
+ "label": "Allow Overtime"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Plan time logs outside Workstation Working Hours.",
- "fieldname": "allow_overtime",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow Overtime",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "depends_on": "eval:!doc.disable_capacity_planning",
+ "fieldname": "allow_production_on_holidays",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Allow Production on Holidays"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "allow_production_on_holidays",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Allow Production on Holidays",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "30",
+ "depends_on": "eval:!doc.disable_capacity_planning",
+ "description": "Try planning operations for X days in advance.",
+ "fieldname": "capacity_planning_for_days",
+ "fieldtype": "Int",
+ "label": "Capacity Planning For (Days)"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "30",
- "description": "Try planning operations for X days in advance.",
- "fieldname": "capacity_planning_for_days",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Capacity Planning For (Days)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!doc.disable_capacity_planning",
+ "description": "Default 10 mins",
+ "fieldname": "mins_between_operations",
+ "fieldtype": "Int",
+ "label": "Time Between Operations (in mins)"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Default 10 mins",
- "fieldname": "mins_between_operations",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Time Between Operations (in mins)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break",
+ "label": "Default Warehouses for Production"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "overproduction_percentage_for_sales_order",
+ "fieldtype": "Percent",
+ "label": "Overproduction Percentage For Sales Order"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "overproduction_percentage_for_sales_order",
- "fieldtype": "Percent",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Overproduction Percentage For Sales Order",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "overproduction_percentage_for_work_order",
+ "fieldtype": "Percent",
+ "label": "Overproduction Percentage For Work Order"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "overproduction_percentage_for_work_order",
- "fieldtype": "Percent",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Overproduction Percentage For Work Order",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "BOM",
+ "fieldname": "backflush_raw_materials_based_on",
+ "fieldtype": "Select",
+ "label": "Backflush Raw Materials Based On",
+ "options": "BOM\nMaterial Transferred for Manufacture"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "BOM",
- "fieldname": "backflush_raw_materials_based_on",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Backflush Raw Materials Based On",
- "length": 0,
- "no_copy": 0,
- "options": "BOM\nMaterial Transferred for Manufacture",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "description": "Allow multiple Material Consumption against a Work Order",
+ "fieldname": "material_consumption",
+ "fieldtype": "Check",
+ "label": "Allow Multiple Material Consumption"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Allow multiple Material Consumption against a Work Order",
- "fieldname": "material_consumption",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow Multiple Material Consumption",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
+ "fieldname": "update_bom_costs_automatically",
+ "fieldtype": "Check",
+ "label": "Update BOM Cost Automatically"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
- "fieldname": "update_bom_costs_automatically",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Update BOM Cost Automatically",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "default_wip_warehouse",
+ "fieldtype": "Link",
+ "label": "Default Work In Progress Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_wip_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Work In Progress Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "default_fg_warehouse",
+ "fieldtype": "Link",
+ "label": "Default Finished Goods Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_fg_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Finished Goods Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "disable_capacity_planning",
+ "fieldtype": "Check",
+ "label": "Disable Capacity Planning"
+ },
+ {
+ "fieldname": "default_scrap_warehouse",
+ "fieldtype": "Link",
+ "label": "Default Scrap Warehouse",
+ "options": "Warehouse"
+ },
+ {
+ "fieldname": "over_production_for_sales_and_work_order_section",
+ "fieldtype": "Section Break",
+ "label": "Over Production for Sales and Work Order"
+ },
+ {
+ "fieldname": "raw_materials_consumption_section",
+ "fieldtype": "Section Break",
+ "label": "Raw Materials Consumption"
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "other_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Other Settings"
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-wrench",
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2018-05-28 00:46:25.310621",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Manufacturing Settings",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "icon": "icon-wrench",
+ "issingle": 1,
+ "modified": "2019-11-26 13:10:45.569341",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Manufacturing Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "read": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 3b24d0f..2b168d1 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -71,12 +71,13 @@
}, __('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;">
<tr><td style="padding-left:25px">
<div>
- <h3>
+ <h3 style="text-decoration: underline;">
<a href = "https://erpnext.com/docs/user/manual/en/stock/projected-quantity">
${__("Projected Quantity Formula")}
</a>
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 0be9f1a..af84481 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -1,1391 +1,321 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2017-10-29 11:53:09.523362",
- "custom": 0,
- "default_print_format": "",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2017-10-29 11:53:09.523362",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "company",
+ "get_items_from",
+ "column_break1",
+ "posting_date",
+ "filters",
+ "item_code",
+ "customer",
+ "warehouse",
+ "project",
+ "column_break2",
+ "from_date",
+ "to_date",
+ "sales_orders_detail",
+ "get_sales_orders",
+ "sales_orders",
+ "material_request_detail",
+ "get_material_request",
+ "material_requests",
+ "select_items_to_manufacture_section",
+ "get_items",
+ "po_items",
+ "material_request_planning",
+ "include_non_stock_items",
+ "include_subcontracted_items",
+ "ignore_existing_ordered_qty",
+ "column_break_25",
+ "for_warehouse",
+ "download_materials_required",
+ "get_items_for_mr",
+ "section_break_27",
+ "mr_items",
+ "other_details",
+ "total_planned_qty",
+ "total_produced_qty",
+ "column_break_32",
+ "status",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fetch_if_empty": 0,
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Naming Series",
- "length": 0,
- "no_copy": 0,
- "options": "MFG-PP-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "MFG-PP-.YYYY.-",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fetch_if_empty": 0,
- "fieldname": "get_items_from",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Get Items From",
- "length": 0,
- "no_copy": 0,
- "options": "\nSales Order\nMaterial Request",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "get_items_from",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Get Items From",
+ "options": "\nSales Order\nMaterial Request"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fetch_if_empty": 0,
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Posting Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Posting Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval: doc.__islocal",
- "columns": 0,
- "depends_on": "eval: doc.get_items_from",
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "filters",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Filters",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "collapsible_depends_on": "eval: doc.__islocal",
+ "depends_on": "eval: doc.get_items_from",
+ "fieldname": "filters",
+ "fieldtype": "Section Break",
+ "label": "Filters"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
- "fetch_if_empty": 0,
- "fieldname": "customer",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Customer",
+ "options": "Customer"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval: doc.get_items_from == \"Material Request\"",
- "fetch_if_empty": 0,
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval: doc.get_items_from == \"Material Request\"",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": "Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
- "fetch_if_empty": 0,
- "fieldname": "project",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "column_break2",
+ "fieldtype": "Column Break",
"width": "50%"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "from_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "label": "From Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "to_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "label": "To Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval: doc.__islocal",
- "columns": 0,
- "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
- "fetch_if_empty": 0,
- "fieldname": "sales_orders_detail",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Orders Detail",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "collapsible_depends_on": "eval: doc.__islocal",
+ "depends_on": "eval: doc.get_items_from == \"Sales Order\"",
+ "fieldname": "sales_orders_detail",
+ "fieldtype": "Section Break",
+ "label": "Sales Orders"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "get_sales_orders",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Get Sales Orders",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "get_sales_orders",
+ "fieldtype": "Button",
+ "label": "Get Sales Orders"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sales_orders",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Orders",
- "length": 0,
- "no_copy": 1,
- "options": "Production Plan Sales Order",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sales_orders",
+ "fieldtype": "Table",
+ "label": "Sales Orders",
+ "no_copy": 1,
+ "options": "Production Plan Sales Order"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval: doc.__islocal",
- "columns": 0,
- "depends_on": "eval: doc.get_items_from == \"Material Request\"",
- "fetch_if_empty": 0,
- "fieldname": "material_request_detail",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Material Request Detail",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "collapsible_depends_on": "eval: doc.__islocal",
+ "depends_on": "eval: doc.get_items_from == \"Material Request\"",
+ "fieldname": "material_request_detail",
+ "fieldtype": "Section Break",
+ "label": "Material Request Detail"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "get_material_request",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Get Material Request",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "get_material_request",
+ "fieldtype": "Button",
+ "label": "Get Material Request"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_requests",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Material Requests",
- "length": 0,
- "no_copy": 1,
- "options": "Production Plan Material Request",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "material_requests",
+ "fieldtype": "Table",
+ "label": "Material Requests",
+ "no_copy": 1,
+ "options": "Production Plan Material Request"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "select_items_to_manufacture_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Select Items to Manufacture",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "select_items_to_manufacture_section",
+ "fieldtype": "Section Break",
+ "label": "Select Items to Manufacture"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "get_items_from",
- "fetch_if_empty": 0,
- "fieldname": "get_items",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Get Items For Work Order",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "get_items_from",
+ "fieldname": "get_items",
+ "fieldtype": "Button",
+ "label": "Get Items For Work Order"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "po_items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 1,
- "options": "Production Plan Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "po_items",
+ "fieldtype": "Table",
+ "no_copy": 1,
+ "options": "Production Plan Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "material_request_planning",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Material Request Planning",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "material_request_planning",
+ "fieldtype": "Section Break",
+ "label": "Material Request Planning"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fetch_if_empty": 0,
- "fieldname": "include_non_stock_items",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Include Non Stock Items",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "1",
+ "fieldname": "include_non_stock_items",
+ "fieldtype": "Check",
+ "label": "Include Non Stock Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fetch_if_empty": 0,
- "fieldname": "include_subcontracted_items",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Include Subcontracted Items",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "1",
+ "fieldname": "include_subcontracted_items",
+ "fieldtype": "Check",
+ "label": "Include Subcontracted Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "If enabled, then system will create the material even if the raw materials are available",
- "fetch_if_empty": 0,
- "fieldname": "ignore_existing_ordered_qty",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Ignore Existing Projected Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "description": "To know more about projected quantity, <a href=\"https://erpnext.com/docs/user/manual/en/stock/projected-quantity\" style=\"text-decoration: underline;\" target=\"_blank\">click here</a>.",
+ "fieldname": "ignore_existing_ordered_qty",
+ "fieldtype": "Check",
+ "label": "Ignore Existing Projected Quantity"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_25",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_25",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "for_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "For Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "label": "For Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "download_materials_required",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Download Materials Required",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "download_materials_required",
+ "fieldtype": "Button",
+ "label": "Download Required Materials"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "get_items_for_mr",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Get Raw Materials For Production",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "get_items_for_mr",
+ "fieldtype": "Button",
+ "label": "Get Raw Materials For Production"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "section_break_27",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_27",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "mr_items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Material Request Plan Item",
- "length": 0,
- "no_copy": 1,
- "options": "Material Request Plan Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "mr_items",
+ "fieldtype": "Table",
+ "label": "Material Request Plan Item",
+ "no_copy": 1,
+ "options": "Material Request Plan Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fetch_if_empty": 0,
- "fieldname": "projected_qty_formula",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Projected Qty Formula",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "fieldname": "other_details",
+ "fieldtype": "Section Break",
+ "label": "Other Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "other_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Other Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "total_planned_qty",
+ "fieldtype": "Float",
+ "label": "Total Planned Qty",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "total_planned_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Planned Qty",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "total_produced_qty",
+ "fieldtype": "Float",
+ "label": "Total Produced Qty",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "total_produced_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Produced Qty",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_32",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_32",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "no_copy": 1,
+ "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled\nMaterial Requested",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Draft",
- "fetch_if_empty": 0,
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 1,
- "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled\nMaterial Requested",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Production Plan",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Production Plan",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-calendar",
- "idx": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-04-09 12:05:14.300886",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Production Plan",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-calendar",
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2019-12-04 15:58:50.940460",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "ASC",
- "title_field": "",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 25c385f..8876253 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -615,6 +615,9 @@
doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
+ if not po_items:
+ frappe.throw(_("Items are required to pull the raw materials which is associated with it."))
+
company = doc.get('company')
warehouse = doc.get('for_warehouse')
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
index 91c2855..09ec24a 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
@@ -6,7 +6,7 @@
'fieldname': 'production_plan',
'transactions': [
{
- 'label': _('Related'),
+ 'label': _('Transactions'),
'items': ['Work Order', 'Material Request']
},
]
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index ea2e7a9..0a8f41f 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -5,10 +5,10 @@
from __future__ import unicode_literals
import unittest
import frappe
-from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
+from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
-from erpnext.manufacturing.doctype.work_order.work_order \
- import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError
+from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
+ ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
@@ -307,14 +307,50 @@
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
+ frappe.db.set_value("Manufacturing Settings",
+ None, "disable_capacity_planning", 0)
+
bom, bom_item = data
bom_doc = frappe.get_doc('BOM', bom)
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
+ self.assertTrue(work_order.planned_end_date)
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
self.assertEqual(len(job_cards), len(bom_doc.operations))
+ def test_capcity_planning(self):
+ frappe.db.set_value("Manufacturing Settings", None, {
+ "disable_capacity_planning": 0,
+ "capacity_planning_for_days": 1
+ })
+
+ data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2',
+ 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+
+ if data:
+ bom, bom_item = data
+
+ planned_start_date = add_months(today(), months=-1)
+ work_order = make_wo_order_test_record(item=bom_item,
+ qty=10, bom_no=bom, planned_start_date=planned_start_date)
+
+ work_order1 = make_wo_order_test_record(item=bom_item,
+ qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1)
+
+ self.assertRaises(CapacityError, work_order1.submit)
+
+ frappe.db.set_value("Manufacturing Settings", None, {
+ "capacity_planning_for_days": 30
+ })
+
+ work_order1.reload()
+ work_order1.submit()
+ self.assertTrue(work_order1.docstatus, 1)
+
+ work_order1.cancel()
+ work_order.cancel()
+
def test_work_order_with_non_transfer_item(self):
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
for item, allow_transfer in items.items():
@@ -371,14 +407,12 @@
wo_order.skip_transfer=1
wo_order.get_items_and_operations_from_bom()
wo_order.sales_order = args.sales_order or None
+ wo_order.planned_start_date = args.planned_start_date or now()
if args.source_warehouse:
for item in wo_order.get("required_items"):
item.source_warehouse = args.source_warehouse
- if args.planned_start_date:
- wo_order.planned_start_date = args.planned_start_date
-
if not args.do_not_save:
wo_order.insert()
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 107c79b..8ca8917 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -6,6 +6,7 @@
frm.custom_make_buttons = {
'Stock Entry': 'Start',
'Pick List': 'Create Pick List',
+ 'Job Card': 'Create Job Card'
};
// Set query for warehouses
@@ -131,7 +132,8 @@
}
if (frm.doc.docstatus===1) {
- frm.trigger('show_progress');
+ frm.trigger('show_progress_for_items');
+ frm.trigger('show_progress_for_operations');
}
if (frm.doc.docstatus === 1
@@ -179,89 +181,72 @@
make_job_card: function(frm) {
let qty = 0;
- const fields = [{
- fieldtype: "Link",
- fieldname: "operation",
- options: "Operation",
- label: __("Operation"),
- get_query: () => {
- const filter_workstation = frm.doc.operations.filter(d => {
- if (d.status != "Completed") {
- return d;
- }
- });
+ let operations_data = [];
- return {
- filters: {
- name: ["in", (filter_workstation || []).map(d => d.operation)]
- }
- };
- },
- reqd: true
- }, {
- fieldtype: "Link",
- fieldname: "workstation",
- options: "Workstation",
- label: __("Workstation"),
- get_query: () => {
- const operation = dialog.get_value("operation");
- const filter_workstation = frm.doc.operations.filter(d => {
- if (d.operation == operation) {
- return d;
- }
- });
-
- return {
- filters: {
- name: ["in", (filter_workstation || []).map(d => d.workstation)]
- }
- };
- },
- onchange: () => {
- const operation = dialog.get_value("operation");
- const workstation = dialog.get_value("workstation");
- if (operation && workstation) {
- const row = frm.doc.operations.filter(d => d.operation == operation && d.workstation == workstation)[0];
- qty = frm.doc.qty - row.completed_qty;
-
- if (qty > 0) {
- dialog.set_value("qty", qty);
- }
- }
- },
- reqd: true
- }, {
- fieldtype: "Float",
- fieldname: "qty",
- label: __("For Quantity"),
- reqd: true
- }];
-
- const dialog = frappe.prompt(fields, function(data) {
- if (data.qty > qty) {
- frappe.throw(__("For Quantity must be less than quantity {0}", [qty]));
+ const dialog = frappe.prompt({fieldname: 'operations', fieldtype: 'Table', label: __('Operations'),
+ fields: [
+ {
+ fieldtype:'Link',
+ fieldname:'operation',
+ label: __('Operation'),
+ read_only:1,
+ in_list_view:1
+ },
+ {
+ fieldtype:'Link',
+ fieldname:'workstation',
+ label: __('Workstation'),
+ read_only:1,
+ in_list_view:1
+ },
+ {
+ fieldtype:'Data',
+ fieldname:'name',
+ label: __('Operation Id')
+ },
+ {
+ fieldtype:'Float',
+ fieldname:'pending_qty',
+ label: __('Pending Qty'),
+ },
+ {
+ fieldtype:'Float',
+ fieldname:'qty',
+ label: __('Quantity to Manufacture'),
+ read_only:0,
+ in_list_view:1,
+ },
+ ],
+ data: operations_data,
+ in_place_edit: true,
+ get_data: function() {
+ return operations_data;
}
-
- if (data.qty <= 0) {
- frappe.throw(__("For Quantity must be greater than zero"));
- }
-
+ }, function(data) {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
args: {
work_order: frm.doc.name,
- operation: data.operation,
- workstation: data.workstation,
- qty: data.qty
- },
- callback: function(r){
- if (r.message) {
- var doc = frappe.model.sync(r.message)[0];
- frappe.set_route("Form", doc.doctype, doc.name);
- }
+ operations: data.operations,
}
});
- }, __("For Job Card"));
+ }, __("Job Card"), __("Create"));
+
+ var pending_qty = 0;
+ frm.doc.operations.forEach(data => {
+ if(data.completed_qty != frm.doc.qty) {
+ pending_qty = frm.doc.qty - flt(data.completed_qty);
+
+ dialog.fields_dict.operations.df.data.push({
+ 'name': data.name,
+ 'operation': data.operation,
+ 'workstation': data.workstation,
+ 'qty': pending_qty,
+ 'pending_qty': pending_qty,
+ });
+ }
+ });
+ dialog.fields_dict.operations.grid.refresh();
},
make_bom: function(frm) {
@@ -277,7 +262,7 @@
});
},
- show_progress: function(frm) {
+ show_progress_for_items: function(frm) {
var bars = [];
var message = '';
var added_min = false;
@@ -311,6 +296,44 @@
frm.dashboard.add_progress(__('Status'), bars, message);
},
+ show_progress_for_operations: function(frm) {
+ if (frm.doc.operations && frm.doc.operations.length) {
+
+ let progress_class = {
+ "Work in Progress": "progress-bar-warning",
+ "Completed": "progress-bar-success"
+ };
+
+ let bars = [];
+ let message = '';
+ let title = '';
+ let status_wise_oprtation_data = {};
+ let total_completed_qty = frm.doc.qty * frm.doc.operations.length;
+
+ frm.doc.operations.forEach(d => {
+ if (!status_wise_oprtation_data[d.status]) {
+ status_wise_oprtation_data[d.status] = [d.completed_qty, d.operation];
+ } else {
+ status_wise_oprtation_data[d.status][0] += d.completed_qty;
+ status_wise_oprtation_data[d.status][1] += ', ' + d.operation;
+ }
+ });
+
+ for (let key in status_wise_oprtation_data) {
+ title = __("{0} Operations: {1}", [key, status_wise_oprtation_data[key][1].bold()]);
+ bars.push({
+ 'title': title,
+ 'width': status_wise_oprtation_data[key][0] / total_completed_qty * 100 + '%',
+ 'progress_class': progress_class[key]
+ });
+
+ message += title + '. ';
+ }
+
+ frm.dashboard.add_progress(__('Status'), bars, message);
+ }
+ },
+
production_item: function(frm) {
if (frm.doc.production_item) {
frappe.call({
@@ -551,6 +574,7 @@
if (!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse);
frm.set_value("fg_warehouse", r.message.fg_warehouse);
+ frm.set_value("scrap_warehouse", r.message.scrap_warehouse);
}
}
});
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 0d073a2..6152fbb 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-10 16:34:16",
@@ -468,7 +469,8 @@
"idx": 1,
"image_field": "image",
"is_submittable": 1,
- "modified": "2019-08-28 12:29:35.315239",
+ "links": [],
+ "modified": "2019-12-04 11:20:04.695123",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 2c16bbe..227ef78 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -6,13 +6,13 @@
import json
import math
from frappe import _
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
-from erpnext.projects.doctype.timesheet.timesheet import OverlapError
+from erpnext.manufacturing.doctype.job_card.job_card import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
@@ -22,6 +22,7 @@
from frappe.model.mapper import get_mapped_doc
class OverProductionError(frappe.ValidationError): pass
+class CapacityError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
class OperationTooLongError(frappe.ValidationError): pass
class ItemHasVariantError(frappe.ValidationError): pass
@@ -260,12 +261,50 @@
self.update_reserved_qty_for_production()
def create_job_card(self):
- for row in self.operations:
+ manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
+
+ enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
+ plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
+
+ for i, row in enumerate(self.operations):
+ self.set_operation_start_end_time(i, row)
+
if not row.workstation:
frappe.throw(_("Row {0}: select the workstation against the operation {1}")
.format(row.idx, row.operation))
- create_job_card(self, row, auto_create=True)
+ original_start_time = row.planned_start_time
+ job_card_doc = create_job_card(self, row,
+ enable_capacity_planning=enable_capacity_planning, auto_create=True)
+
+ if enable_capacity_planning and job_card_doc:
+ row.planned_start_time = job_card_doc.time_logs[0].from_time
+ row.planned_end_time = job_card_doc.time_logs[-1].to_time
+
+ if date_diff(row.planned_start_time, original_start_time) > plan_days:
+ frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
+ .format(plan_days, row.operation), CapacityError)
+
+ row.db_update()
+
+ planned_end_date = self.operations and self.operations[-1].planned_end_time
+ if planned_end_date:
+ self.db_set("planned_end_date", planned_end_date)
+
+ def set_operation_start_end_time(self, idx, row):
+ """Set start and end time for given operation. If first operation, set start as
+ `planned_start_date`, else add time diff to end time of earlier operation."""
+ if idx==0:
+ # first operation at planned_start date
+ row.planned_start_time = self.planned_start_date
+ else:
+ row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\
+ + get_mins_between_operations()
+
+ row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins)
+
+ if row.planned_start_time == row.planned_end_time:
+ frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time"))
def validate_cancel(self):
if self.status == "Stopped":
@@ -327,9 +366,8 @@
"""Fetch operations from BOM and set in 'Work Order'"""
self.set('operations', [])
- if not self.bom_no \
- or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
- return
+ if not self.bom_no:
+ return
if self.use_multi_level_bom:
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
@@ -681,11 +719,13 @@
@frappe.whitelist()
def get_default_warehouse():
- wip_warehouse = frappe.db.get_single_value("Manufacturing Settings",
- "default_wip_warehouse")
- fg_warehouse = frappe.db.get_single_value("Manufacturing Settings",
- "default_fg_warehouse")
- return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
+ doc = frappe.get_cached_doc("Manufacturing Settings")
+
+ return {
+ "wip_warehouse": doc.default_wip_warehouse,
+ "fg_warehouse": doc.default_fg_warehouse,
+ "scrap_warehouse": doc.default_scrap_warehouse
+ }
@frappe.whitelist()
def stop_unstop(work_order, status):
@@ -715,21 +755,41 @@
return out
@frappe.whitelist()
-def make_job_card(work_order, operation, workstation, qty=0):
- work_order = frappe.get_doc('Work Order', work_order)
- row = get_work_order_operation_data(work_order, operation, workstation)
- if row:
- return create_job_card(work_order, row, qty)
+def make_job_card(work_order, operations):
+ if isinstance(operations, string_types):
+ operations = json.loads(operations)
-def create_job_card(work_order, row, qty=0, auto_create=False):
+ work_order = frappe.get_doc('Work Order', work_order)
+ for row in operations:
+ validate_operation_data(row)
+ create_job_card(work_order, row, row.get("qty"), auto_create=True)
+
+def validate_operation_data(row):
+ if row.get("qty") <= 0:
+ frappe.throw(_("Quantity to Manufacture can not be zero for the operation {0}")
+ .format(
+ frappe.bold(row.get("operation"))
+ )
+ )
+
+ if row.get("qty") > row.get("pending_qty"):
+ frappe.throw(_("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})")
+ .format(
+ frappe.bold(row.get("operation")),
+ frappe.bold(row.get("qty")),
+ frappe.bold(row.get("pending_qty"))
+ )
+ )
+
+def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False):
doc = frappe.new_doc("Job Card")
doc.update({
'work_order': work_order.name,
- 'operation': row.operation,
- 'workstation': row.workstation,
+ 'operation': row.get("operation"),
+ 'workstation': row.get("workstation"),
'posting_date': nowdate(),
'for_quantity': qty or work_order.get('qty', 0),
- 'operation_id': row.name,
+ 'operation_id': row.get("name"),
'bom_no': work_order.bom_no,
'project': work_order.project,
'company': work_order.company,
@@ -741,8 +801,11 @@
if auto_create:
doc.flags.ignore_mandatory = True
+ if enable_capacity_planning:
+ doc.schedule_time_logs(row)
+
doc.insert()
- frappe.msgprint(_("Job card {0} created").format(doc.name))
+ frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)))
return doc
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_calendar.js b/erpnext/manufacturing/doctype/work_order/work_order_calendar.js
index c44b1e2..7ce05e9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_calendar.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order_calendar.js
@@ -2,11 +2,13 @@
// For license information, please see license.txt
frappe.views.calendar["Work Order"] = {
+ fields: ["planned_start_date", "planned_end_date", "status", "produced_qty", "qty", "name", "name"],
field_map: {
"start": "planned_start_date",
"end": "planned_end_date",
"id": "name",
"title": "name",
+ "status": "status",
"allDay": "allDay",
"progress": function(data) {
return flt(data.produced_qty) / data.qty * 100;
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 0d3c30e..87c090f 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -6,7 +6,8 @@
'fieldname': 'work_order',
'transactions': [
{
- 'items': ['Pick List', 'Stock Entry', 'Job Card']
+ 'label': _('Transactions'),
+ 'items': ['Stock Entry', 'Job Card', 'Pick List']
}
]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
index 75d42cd..3f5e18e 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2014-10-16 14:35:41.950175",
"doctype": "DocType",
"editable_grid": 1,
@@ -68,6 +69,7 @@
"description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty",
"fieldtype": "Float",
+ "in_list_view": 1,
"label": "Completed Qty",
"no_copy": 1,
"read_only": 1
@@ -188,8 +190,9 @@
}
],
"istable": 1,
- "modified": "2019-07-16 23:01:07.720337",
- "modified_by": "govindsmenokee@gmail.com",
+ "links": [],
+ "modified": "2019-12-03 19:24:29.594189",
+ "modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Operation",
"owner": "Administrator",
@@ -197,4 +200,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json
index dca9891..d130391 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.json
+++ b/erpnext/manufacturing/doctype/workstation/workstation.json
@@ -1,466 +1,159 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:workstation_name",
- "beta": 0,
- "creation": "2013-01-10 16:34:17",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:workstation_name",
+ "creation": "2013-01-10 16:34:17",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "field_order": [
+ "workstation_name",
+ "production_capacity",
+ "column_break_3",
+ "over_heads",
+ "hour_rate_electricity",
+ "hour_rate_consumable",
+ "column_break_11",
+ "hour_rate_rent",
+ "hour_rate_labour",
+ "hour_rate",
+ "working_hours_section",
+ "holiday_list",
+ "working_hours",
+ "workstaion_description",
+ "description"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "description_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "workstation_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Workstation Name",
+ "oldfieldname": "workstation_name",
+ "oldfieldtype": "Data",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "workstation_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Workstation Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "workstation_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
"width": "300px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "over_heads",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Operating Costs",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "over_heads",
+ "fieldtype": "Section Break",
+ "label": "Operating Costs",
+ "oldfieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "per hour",
- "fieldname": "hour_rate_electricity",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Electricity Cost",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hour_rate_electricity",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "bold": 1,
+ "description": "per hour",
+ "fieldname": "hour_rate_electricity",
+ "fieldtype": "Currency",
+ "label": "Electricity Cost",
+ "oldfieldname": "hour_rate_electricity",
+ "oldfieldtype": "Currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "per hour",
- "fieldname": "hour_rate_consumable",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Consumable Cost",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hour_rate_consumable",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "bold": 1,
+ "description": "per hour",
+ "fieldname": "hour_rate_consumable",
+ "fieldtype": "Currency",
+ "label": "Consumable Cost",
+ "oldfieldname": "hour_rate_consumable",
+ "oldfieldtype": "Currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "per hour",
- "fieldname": "hour_rate_rent",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Rent Cost",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hour_rate_rent",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "bold": 1,
+ "description": "per hour",
+ "fieldname": "hour_rate_rent",
+ "fieldtype": "Currency",
+ "label": "Rent Cost",
+ "oldfieldname": "hour_rate_rent",
+ "oldfieldtype": "Currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Wages per hour",
- "fieldname": "hour_rate_labour",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Wages",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hour_rate_labour",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "bold": 1,
+ "description": "Wages per hour",
+ "fieldname": "hour_rate_labour",
+ "fieldtype": "Currency",
+ "label": "Wages",
+ "oldfieldname": "hour_rate_labour",
+ "oldfieldtype": "Currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "per hour",
- "fieldname": "hour_rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Net Hour Rate",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hour_rate",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "description": "per hour",
+ "fieldname": "hour_rate",
+ "fieldtype": "Currency",
+ "label": "Net Hour Rate",
+ "oldfieldname": "hour_rate",
+ "oldfieldtype": "Currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "working_hours_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Working Hours",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "working_hours_section",
+ "fieldtype": "Section Break",
+ "label": "Working Hours"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "working_hours",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Working Hours",
- "length": 0,
- "no_copy": 0,
- "options": "Workstation Working Hour",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "working_hours",
+ "fieldtype": "Table",
+ "label": "Working Hours",
+ "options": "Workstation Working Hour"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "holiday_list",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Holiday List",
- "length": 0,
- "no_copy": 0,
- "options": "Holiday List",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "holiday_list",
+ "fieldtype": "Link",
+ "label": "Holiday List",
+ "options": "Holiday List"
+ },
+ {
+ "default": "1",
+ "fieldname": "production_capacity",
+ "fieldtype": "Int",
+ "label": "Production Capacity",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "workstaion_description",
+ "fieldtype": "Section Break",
+ "label": "Description"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-wrench",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-07-18 22:28:50.163219",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Workstation",
- "owner": "Administrator",
+ ],
+ "icon": "icon-wrench",
+ "idx": 1,
+ "modified": "2019-11-26 12:39:19.742052",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Workstation",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 1,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "show_name_in_global_search": 1,
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index f6ab72a..3512e59 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -4,7 +4,9 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, cint, getdate, formatdate, comma_and, time_diff_in_seconds, to_timedelta
+from erpnext.support.doctype.issue.issue import get_holidays
+from frappe.utils import (flt, cint, getdate, formatdate,
+ comma_and, time_diff_in_seconds, to_timedelta, add_days)
from frappe.model.document import Document
from dateutil.parser import parse
@@ -43,6 +45,17 @@
where parent = %s and workstation = %s""",
(self.hour_rate, bom_no[0], self.name))
+ def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False):
+ if not skip_holiday_list_check and (not self.holiday_list or
+ cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))):
+ return schedule_date
+
+ if schedule_date in tuple(get_holidays(self.holiday_list)):
+ schedule_date = add_days(schedule_date, 1)
+ self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True)
+
+ return schedule_date
+
@frappe.whitelist()
def get_default_holiday_list():
return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list")
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
index 9e0d1d1..3ddbe73 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
@@ -6,8 +6,14 @@
'fieldname': 'workstation',
'transactions': [
{
- 'label': _('Manufacture'),
- 'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet']
+ 'label': _('Master'),
+ 'items': ['BOM', 'Routing', 'Operation']
+ },
+ {
+ 'label': _('Transaction'),
+ 'items': ['Work Order', 'Job Card', 'Timesheet']
}
- ]
+ ],
+ 'disable_create_buttons': ['BOM', 'Routing', 'Operation',
+ 'Work Order', 'Job Card', 'Timesheet']
}
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 875d115..48907ad 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -14,7 +14,7 @@
def get_data(filters, data):
get_exploded_items(filters.bom, data)
-def get_exploded_items(bom, data, indent=0):
+def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all("BOM Item",
filters={"parent": bom},
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
@@ -26,13 +26,13 @@
'item_name': item.item_name,
'indent': indent,
'bom': item.bom_no,
- 'qty': item.qty,
+ 'qty': item.qty * qty,
'uom': item.uom,
'description': item.description,
'scrap': item.scrap
})
if item.bom_no:
- get_exploded_items(item.bom_no, data, indent=indent+1)
+ get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
def get_columns():
return [
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index be016ad..f7b407b 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils.data import comma_and
def execute(filters=None):
# if not filters: filters = {}
@@ -13,35 +14,36 @@
data = get_bom_stock(filters)
qty_to_make = filters.get("qty_to_make")
+ manufacture_details = get_manufacturer_records()
for row in data:
- item_map = get_item_details(row.item_code)
reqd_qty = qty_to_make * row.actual_qty
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
- if row.to_build > 0:
- diff_qty = row.to_build - reqd_qty
- summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, row.to_build, reqd_qty, diff_qty, last_pur_price])
- else:
- diff_qty = 0 - reqd_qty
- summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, "0.000", reqd_qty, diff_qty, last_pur_price])
+ summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
return columns, summ_data
+def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
+ to_build = row.to_build if row.to_build > 0 else 0
+ diff_qty = to_build - reqd_qty
+ return [row.item_code, row.description,
+ comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer', []), add_quotes=False),
+ comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer_part', []), add_quotes=False),
+ row.actual_qty, str(to_build),
+ reqd_qty, diff_qty, last_pur_price]
+
def get_columns():
"""return columns"""
columns = [
_("Item") + ":Link/Item:100",
_("Description") + "::150",
- _("Manufacturer") + "::100",
- _("Manufacturer Part Number") + "::100",
+ _("Manufacturer") + "::250",
+ _("Manufacturer Part Number") + "::250",
_("Qty") + ":Float:50",
_("Stock Qty") + ":Float:100",
_("Reqd Qty")+ ":Float:100",
_("Diff Qty")+ ":Float:100",
_("Last Purchase Price")+ ":Float:100",
-
-
]
-
return columns
def get_bom_stock(filters):
@@ -85,7 +87,12 @@
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
-def get_item_details(item_code):
- items = frappe.db.sql("""select it.item_group, it.item_name, it.stock_uom, it.name, it.brand, it.description, it.manufacturer_part_no, it.manufacturer from tabItem it where it.item_code = %s""", item_code, as_dict=1)
+def get_manufacturer_records():
+ details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"])
+ manufacture_details = frappe._dict()
+ for detail in details:
+ dic = manufacture_details.setdefault(detail.get('parent'), {})
+ dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
+ dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
- return dict((d.name, d) for d in items)
+ return manufacture_details
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 07b646b..e26b1c8 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -645,5 +645,9 @@
erpnext.patches.v12_0.set_payment_entry_status
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
+erpnext.patches.v12_0.add_export_type_field_in_party_master
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
-erpnext.patches.v12_0.update_price_or_product_discount
\ No newline at end of file
+erpnext.patches.v12_0.update_price_or_product_discount
+erpnext.patches.v12_0.set_production_capacity_in_workstation
+erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order
+erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py
new file mode 100644
index 0000000..c565b7e
--- /dev/null
+++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py
@@ -0,0 +1,40 @@
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.india.setup import make_custom_fields
+
+def execute():
+
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ make_custom_fields()
+
+ frappe.reload_doctype('Tax Category')
+ frappe.reload_doctype('Sales Taxes and Charges Template')
+ frappe.reload_doctype('Purchase Taxes and Charges Template')
+
+ # Create tax category with inter state field checked
+ tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name')
+
+ if not tax_category:
+ inter_state_category = frappe.get_doc({
+ 'doctype': 'Tax Category',
+ 'title': 'OUT OF STATE',
+ 'is_inter_state': 1
+ }).insert()
+
+ tax_category = inter_state_category.name
+
+ for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'):
+ template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name'])
+ if template:
+ frappe.db.set_value(doctype, template, 'tax_category', tax_category)
+
+ frappe.db.sql("""
+ DELETE FROM `tabCustom Field`
+ WHERE fieldname = 'is_inter_state'
+ AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template')
+ """)
+
+
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index f25b9ea..e47344b 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -62,12 +62,12 @@
]
for dt in doctypes:
- for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item`
+ for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
where ifnull(item_tax_rate, '') not in ('', '{{}}')
and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate)
item_tax_template_name = get_item_tax_template(item_tax_templates,
- item_tax_map, d.item_code, d.parent)
+ item_tax_map, d.item_code, d.parenttype, d.parent)
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
frappe.db.auto_commit_on_many_writes = False
@@ -77,7 +77,7 @@
settings.determine_address_tax_category_from = "Billing Address"
settings.save()
-def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None):
+def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
# search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in iteritems(item_tax_templates):
if item_tax_map == item_tax_template_map:
@@ -88,23 +88,44 @@
item_tax_template.title = make_autoname("Item Tax Template-.####")
for tax_type, tax_rate in iteritems(item_tax_map):
- if not frappe.db.exists("Account", tax_type):
+ account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1)
+ if account_details:
+ if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
+ frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
+ else:
parts = tax_type.strip().split(" - ")
account_name = " - ".join(parts[:-1])
- company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
+ company = get_company(parts[-1], parenttype, parent)
parent_account = frappe.db.get_value("Account",
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
-
- frappe.get_doc({
- "doctype": "Account",
+ filters = {
"account_name": account_name,
- "company": company,
- "account_type": "Tax",
- "parent_account": parent_account
- }).insert()
+ "company": company,
+ "account_type": "Tax",
+ "parent_account": parent_account
+ }
+ tax_type = frappe.db.get_value("Account", filters)
+ if not tax_type:
+ account = frappe.new_doc("Account")
+ account.update(filters)
+ account.insert()
+ tax_type = account.name
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
item_tax_templates.setdefault(item_tax_template.title, {})
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
item_tax_template.save()
return item_tax_template.name
+
+def get_company(company_abbr, parenttype=None, parent=None):
+ if parenttype and parent:
+ company = frappe.get_cached_value(parenttype, parent, 'company')
+ else:
+ company = frappe.db.get_value("Company", filters={"abbr": company_abbr})
+
+ if not company:
+ companies = frappe.get_all('Company')
+ if len(companies) == 1:
+ company = companies[0].name
+
+ return company
diff --git a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py
new file mode 100644
index 0000000..555d8ae
--- /dev/null
+++ b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py
@@ -0,0 +1,9 @@
+import frappe
+def execute():
+ for doctype in ['Sales Order Item', 'Purchase Order Item']:
+ frappe.reload_doctype(doctype)
+ frappe.db.sql("""
+ UPDATE `tab{0}`
+ SET against_blanket_order = 1
+ WHERE ifnull(blanket_order, '') != ''
+ """.format(doctype))
diff --git a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py
new file mode 100644
index 0000000..8ba0d79
--- /dev/null
+++ b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py
@@ -0,0 +1,8 @@
+import frappe
+def execute():
+ frappe.reload_doc('hr', 'doctype', 'expense_claim_detail')
+ frappe.db.sql("""
+ UPDATE `tabExpense Claim Detail` child, `tabExpense Claim` par
+ SET child.cost_center = par.cost_center
+ WHERE child.parent = par.name
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py
index 54bc5b3..55bbdee 100644
--- a/erpnext/patches/v12_0/set_gst_category.py
+++ b/erpnext/patches/v12_0/set_gst_category.py
@@ -7,6 +7,8 @@
if not company:
return
+ frappe.reload_doc('accounts', 'doctype', 'Tax Category')
+
make_custom_fields()
for doctype in ['Sales Invoice', 'Purchase Invoice']:
diff --git a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py
new file mode 100644
index 0000000..bae1e28
--- /dev/null
+++ b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("manufacturing", "doctype", "workstation")
+
+ frappe.db.sql(""" UPDATE `tabWorkstation`
+ SET production_capacity = 1 """)
\ No newline at end of file
diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py
index b262c46..bf5c402 100644
--- a/erpnext/portal/doctype/homepage/test_homepage.py
+++ b/erpnext/portal/doctype/homepage/test_homepage.py
@@ -5,7 +5,7 @@
import frappe
import unittest
-from frappe.tests.test_website import set_request
+from frappe.utils import set_request
from frappe.website.render import render
class TestHomepage(unittest.TestCase):
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index c52b7a9..5b3196d 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -6,7 +6,7 @@
import frappe
import unittest
from bs4 import BeautifulSoup
-from frappe.tests.test_website import set_request
+from frappe.utils import set_request
from frappe.website.render import render
class TestHomepageSection(unittest.TestCase):
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index a534e5f..97042db 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -2,7 +2,7 @@
from bs4 import BeautifulSoup
import frappe, unittest
-from frappe.tests.test_website import set_request, get_html_for_route
+from frappe.utils import set_request, get_html_for_route
from frappe.website.render import render
from erpnext.portal.product_configurator.utils import get_products_for_website
from erpnext.stock.doctype.item.test_item import make_item_variant
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 61c50e5..3a373a4 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -313,13 +313,25 @@
search_condition = ''
if search:
+ # Default fields to search from
+ default_fields = {'name', 'item_name', 'description', 'item_group'}
+
+ # Get meta search fields
+ meta = frappe.get_meta("Item")
+ meta_fields = set(meta.get_search_fields())
+
+ # Join the meta fields and default fields set
+ search_fields = default_fields.union(meta_fields)
+ try:
+ if frappe.db.count('Item', cache=True) > 50000:
+ search_fields.remove('description')
+ except KeyError:
+ pass
+
+ # Build or filters for query
search = '%{}%'.format(search)
- or_filters = [
- ['name', 'like', search],
- ['item_name', 'like', search],
- ['description', 'like', search],
- ['item_group', 'like', search]
- ]
+ or_filters = [[field, 'like', search] for field in search_fields]
+
search_condition = get_conditions(or_filters, 'or')
filter_condition = get_conditions(filters, 'and')
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 25c97d1..3570a0f 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -4,20 +4,16 @@
setup(frm) {
frm.make_methods = {
'Timesheet': () => {
- let doctype = 'Timesheet';
- frappe.model.with_doctype(doctype, () => {
- let new_doc = frappe.model.get_new_doc(doctype);
-
- // add a new row and set the project
- let time_log = frappe.model.get_new_doc('Timesheet Detail');
- time_log.project = frm.doc.name;
- time_log.parent = new_doc.name;
- time_log.parentfield = 'time_logs';
- time_log.parenttype = 'Timesheet';
- new_doc.time_logs = [time_log];
-
- frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
- });
+ open_form(frm, "Timesheet", "Timesheet Detail", "time_logs");
+ },
+ 'Purchase Order': () => {
+ open_form(frm, "Purchase Order", "Purchase Order Item", "items");
+ },
+ 'Purchase Receipt': () => {
+ open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items");
+ },
+ 'Purchase Invoice': () => {
+ open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items");
},
};
},
@@ -80,7 +76,7 @@
frm.events.set_status(frm, 'Cancelled');
}, __('Set Status'));
}
-
+
if (frappe.model.can_read("Task")) {
frm.add_custom_button(__("Gantt Chart"), function () {
frappe.route_options = {
@@ -123,3 +119,20 @@
},
});
+
+function open_form(frm, doctype, child_doctype, parentfield) {
+ frappe.model.with_doctype(doctype, () => {
+ let new_doc = frappe.model.get_new_doc(doctype);
+
+ // add a new row and set the project
+ let new_child_doc = frappe.model.get_new_doc(child_doctype);
+ new_child_doc.project = frm.doc.name;
+ new_child_doc.parent = new_doc.name;
+ new_child_doc.parentfield = parentfield;
+ new_child_doc.parenttype = doctype;
+ new_doc[parentfield] = [new_child_doc];
+
+ frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
+ });
+
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index c4481c9..e908216 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -188,7 +188,8 @@
}, as_dict=True)
# check internal overlap
for time_log in self.time_logs:
- if not (time_log.from_time or time_log.to_time): continue
+ if not (time_log.from_time and time_log.to_time
+ and args.from_time and args.to_time): continue
if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \
args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or
diff --git a/erpnext/public/images/illustrations/collaboration.png b/erpnext/public/images/illustrations/collaboration.png
deleted file mode 100644
index 12c67e3..0000000
--- a/erpnext/public/images/illustrations/collaboration.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/customer.png b/erpnext/public/images/illustrations/customer.png
deleted file mode 100644
index b2ddbf3..0000000
--- a/erpnext/public/images/illustrations/customer.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/customers-onboard.png b/erpnext/public/images/illustrations/customers-onboard.png
new file mode 100644
index 0000000..4a517bd
--- /dev/null
+++ b/erpnext/public/images/illustrations/customers-onboard.png
Binary files differ
diff --git a/erpnext/public/images/illustrations/desk-onboard.png b/erpnext/public/images/illustrations/desk-onboard.png
new file mode 100644
index 0000000..74b632d
--- /dev/null
+++ b/erpnext/public/images/illustrations/desk-onboard.png
Binary files differ
diff --git a/erpnext/public/images/illustrations/letterhead-onboard.png b/erpnext/public/images/illustrations/letterhead-onboard.png
new file mode 100644
index 0000000..fdfd16a
--- /dev/null
+++ b/erpnext/public/images/illustrations/letterhead-onboard.png
Binary files differ
diff --git a/erpnext/public/images/illustrations/letterhead.png b/erpnext/public/images/illustrations/letterhead.png
deleted file mode 100644
index 37df6d7..0000000
--- a/erpnext/public/images/illustrations/letterhead.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/onboard.png b/erpnext/public/images/illustrations/onboard.png
deleted file mode 100644
index 094aa3f..0000000
--- a/erpnext/public/images/illustrations/onboard.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/product.png b/erpnext/public/images/illustrations/product.png
deleted file mode 100644
index f864b7a..0000000
--- a/erpnext/public/images/illustrations/product.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/products-onboard.png b/erpnext/public/images/illustrations/products-onboard.png
new file mode 100644
index 0000000..2dee203
--- /dev/null
+++ b/erpnext/public/images/illustrations/products-onboard.png
Binary files differ
diff --git a/erpnext/public/images/illustrations/supplier-onboard.png b/erpnext/public/images/illustrations/supplier-onboard.png
new file mode 100644
index 0000000..30335f2
--- /dev/null
+++ b/erpnext/public/images/illustrations/supplier-onboard.png
Binary files differ
diff --git a/erpnext/public/images/illustrations/supplier.png b/erpnext/public/images/illustrations/supplier.png
deleted file mode 100644
index 87f7789..0000000
--- a/erpnext/public/images/illustrations/supplier.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/illustrations/user.png b/erpnext/public/images/illustrations/user.png
deleted file mode 100644
index 7dd7db2..0000000
--- a/erpnext/public/images/illustrations/user.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 02c3058..926227b 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -30,7 +30,7 @@
&& frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) {
var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total");
- var disable = df.default || cint(frappe.sys_defaults.disable_rounded_total);
+ var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total);
this.frm.set_value("disable_rounded_total", disable);
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 5da9493..1be4f27 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1305,6 +1305,10 @@
me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
}
+ if (d.free_item_data) {
+ me.apply_product_discount(d.free_item_data);
+ }
+
if (d.apply_rule_on_other_items) {
items_rule_dict[d.name] = d;
}
@@ -1334,6 +1338,20 @@
}
},
+ apply_product_discount: function(free_item_data) {
+ const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code
+ && d.is_free_item)) || [];
+
+ if (!items.length) {
+ let row_to_modify = frappe.model.add_child(this.frm.doc,
+ this.frm.doc.doctype + ' Item', 'items');
+
+ for (let key in free_item_data) {
+ row_to_modify[key] = free_item_data[key];
+ }
+ }
+ },
+
apply_price_list: function(item, reset_plc_conversion) {
// We need to reset plc_conversion_rate sometimes because the call to
// `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value
@@ -1716,6 +1734,14 @@
}
},
+ against_blanket_order: function(doc, cdt, cdn) {
+ var item = locals[cdt][cdn];
+ if(!item.against_blanket_order) {
+ frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order", null);
+ frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order_rate", 0.00);
+ }
+ },
+
blanket_order: function(doc, cdt, cdn) {
var me = this;
var item = locals[cdt][cdn];
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 63e057c..dead309 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -4,7 +4,7 @@
"filters": get_filters(),
"formatter": function(value, row, column, data, default_formatter) {
if (column.fieldname=="account") {
- value = data.account_name;
+ value = data.account_name || value;
column.link_onclick =
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
index f151add..54c3597 100644
--- a/erpnext/public/js/hub/PageContainer.vue
+++ b/erpnext/public/js/hub/PageContainer.vue
@@ -24,7 +24,7 @@
function get_route_map() {
const read_only_routes = {
'marketplace/home': Home,
- 'marketplace/search/:keyword': Search,
+ 'marketplace/search/:category/:keyword': Search,
'marketplace/category/:category': Category,
'marketplace/item/:item': Item,
'marketplace/seller/:seller': Seller,
diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue
index 3a0e6bf..057fe8b 100644
--- a/erpnext/public/js/hub/pages/Category.vue
+++ b/erpnext/public/js/hub/pages/Category.vue
@@ -3,6 +3,12 @@
class="marketplace-page"
:data-page-name="page_name"
>
+ <search-input
+ :placeholder="search_placeholder"
+ :on_search="set_search_route"
+ v-model="search_value"
+ />
+
<h5>{{ page_title }}</h5>
<item-cards-container
@@ -26,7 +32,13 @@
item_id_fieldname: 'name',
// Constants
- empty_state_message: __(`No items in this category yet.`)
+ empty_state_message: __(`No items in this category yet.`),
+
+ search_value: '',
+
+ // Constants
+ search_placeholder: __('Search for anything ...'),
+
};
},
computed: {
@@ -35,6 +47,7 @@
}
},
created() {
+ this.search_value = '';
this.get_items();
},
methods: {
@@ -51,7 +64,11 @@
go_to_item_details_page(hub_item_name) {
frappe.set_route(`marketplace/item/${hub_item_name}`);
- }
+ },
+
+ set_search_route() {
+ frappe.set_route('marketplace', 'search', this.category, this.search_value);
+ },
}
}
</script>
diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue
index 3536569..aaeaa7e 100644
--- a/erpnext/public/js/hub/pages/Home.vue
+++ b/erpnext/public/js/hub/pages/Home.vue
@@ -98,7 +98,7 @@
},
set_search_route() {
- frappe.set_route('marketplace', 'search', this.search_value);
+ frappe.set_route('marketplace', 'search', 'All', this.search_value);
},
}
}
diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue
index 5118a81..1032842 100644
--- a/erpnext/public/js/hub/pages/Search.vue
+++ b/erpnext/public/js/hub/pages/Search.vue
@@ -29,8 +29,10 @@
return {
page_name: frappe.get_route()[1],
items: [],
- search_value: frappe.get_route()[2],
+ category: frappe.get_route()[2],
+ search_value: frappe.get_route()[3],
item_id_fieldname: 'name',
+ filters: {},
// Constants
search_placeholder: __('Search for anything ...'),
@@ -40,7 +42,7 @@
computed: {
page_title() {
return this.items.length
- ? __(`Results for "${this.search_value}"`)
+ ? __(`Results for "${this.search_value}" ${this.category !== 'All'? `in category ${this.category}` : ''}`)
: __('No Items found.');
}
},
@@ -49,14 +51,20 @@
},
methods: {
get_items() {
- hub.call('get_items', { keyword: this.search_value })
+ if (this.category !== 'All') {
+ this.filters['hub_category'] = this.category;
+ }
+ hub.call('get_items', {
+ keyword: this.search_value,
+ filters: this.filters
+ })
.then((items) => {
this.items = items;
})
},
set_route_and_get_items() {
- frappe.set_route('marketplace', 'search', this.search_value);
+ frappe.set_route('marketplace', 'search', this.category, this.search_value);
this.get_items();
},
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index 84d2113..560a561 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -65,7 +65,7 @@
frappe.throw(__("Please set {0}",
[__(frappe.meta.get_label(doc.doctype, frappe.dynamic_link.fieldname, doc.name))]));
}
- console.log(frappe.dynamic_link)
+
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index a8d3888..99c1b8a 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -7,6 +7,21 @@
if(!method) {
method = "erpnext.accounts.party.get_party_details";
}
+
+ if (args) {
+ if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
+ if (frm.doc.company_address && (!args.company_address)) {
+ args.company_address = frm.doc.company_address;
+ }
+ }
+
+ if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
+ if (frm.doc.shipping_address && (!args.shipping_address)) {
+ args.shipping_address = frm.doc.shipping_address;
+ }
+ }
+ }
+
if(!args) {
if((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
@@ -30,6 +45,35 @@
};
}
+ if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
+ if (!args) {
+ args = {
+ party: frm.doc.customer || frm.doc.party_name,
+ party_type: 'Customer'
+ }
+ }
+ if (frm.doc.company_address && (!args.company_address)) {
+ args.company_address = frm.doc.company_address;
+ }
+
+ if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) {
+ args.shipping_address_name = frm.doc.shipping_address_name;
+ }
+ }
+
+ if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
+ if (!args) {
+ args = {
+ party: frm.doc.supplier,
+ party_type: 'Supplier'
+ }
+ }
+
+ if (frm.doc.shipping_address && (!args.shipping_address)) {
+ args.shipping_address = frm.doc.shipping_address;
+ }
+ }
+
if (args) {
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
}
diff --git a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json
index 74370cc..a4e6aed 100644
--- a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json
+++ b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json
@@ -13,7 +13,7 @@
"fieldname": "problem",
"fieldtype": "Long Text",
"in_list_view": 1,
- "label": "Problem"
+ "label": "Review"
},
{
"fieldname": "sb_00",
diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
index f5c0fbc..0a67fa5 100644
--- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
+++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
@@ -18,7 +18,7 @@
"fieldname": "procedure",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "Procedure",
+ "label": "Child Procedure",
"options": "Quality Procedure"
}
],
diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js
index e7cc919..7ff4de4 100644
--- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js
+++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js
@@ -3,6 +3,26 @@
frappe.ui.form.on('GST HSN Code', {
refresh: function(frm) {
-
+ if(! frm.doc.__islocal && frm.doc.taxes.length){
+ frm.add_custom_button(__('Update Taxes for Items'), function(){
+ frappe.confirm(
+ 'Are you sure? It will overwrite taxes for all items with HSN Code <b>'+frm.doc.name+'</b>.',
+ function(){
+ frappe.call({
+ args:{
+ taxes: frm.doc.taxes,
+ hsn_code: frm.doc.name
+ },
+ method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master',
+ callback: function(r) {
+ if(r.message){
+ frappe.show_alert(__('Item taxes updated'));
+ }
+ }
+ });
+ }
+ );
+ });
+ }
}
-});
+});
\ No newline at end of file
diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json
index 2a2145c..06dab37 100644
--- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json
+++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json
@@ -1,104 +1,46 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:hsn_code",
- "beta": 0,
- "creation": "2017-06-21 10:48:56.422086",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "field:hsn_code",
+ "creation": "2017-06-21 10:48:56.422086",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "hsn_code",
+ "description",
+ "taxes"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hsn_code",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "HSN Code",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "hsn_code",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "HSN Code",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Description"
+ },
+ {
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Taxes",
+ "options": "Item Tax"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-09-29 14:38:52.220743",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "GST HSN Code",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "hsn_code, description",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "hsn_code",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "modified": "2019-11-01 11:18:59.556931",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "GST HSN Code",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "search_fields": "hsn_code, description",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "hsn_code",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py
index 9637c2e..fa2cb12 100644
--- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py
+++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py
@@ -8,3 +8,22 @@
class GSTHSNCode(Document):
pass
+
+@frappe.whitelist()
+def update_taxes_in_item_master(taxes, hsn_code):
+ items = frappe.get_list("Item", filters={
+ 'gst_hsn_code': hsn_code
+ })
+
+ taxes = frappe.parse_json(taxes)
+ frappe.enqueue(update_item_document, items=items, taxes=taxes)
+ return 1
+
+def update_item_document(items, taxes):
+ for item in items:
+ item_to_be_updated=frappe.get_doc("Item", item.name)
+ item_to_be_updated.taxes = []
+ for tax in taxes:
+ tax = frappe._dict(tax)
+ item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category})
+ item_to_be_updated.save()
\ No newline at end of file
diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
index fef73d9..fa6fb70 100644
--- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
@@ -64,7 +64,8 @@
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250)
- self.assertEqual(output["itc_elg"]["itc_avl"][4]["iamt"], 45)
+ self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
+ self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
def make_sales_invoice():
si = create_sales_invoice(company="_Test Company GST",
@@ -158,10 +159,18 @@
pi.append("taxes", {
"charge_type": "On Net Total",
- "account_head": "IGST - _GST",
+ "account_head": "CGST - _GST",
"cost_center": "Main - _GST",
- "description": "IGST @ 18.0",
- "rate": 18
+ "description": "CGST @ 9.0",
+ "rate": 9
+ })
+
+ pi.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "SGST - _GST",
+ "cost_center": "Main - _GST",
+ "description": "SGST @ 9.0",
+ "rate": 9
})
pi.submit()
diff --git a/erpnext/regional/germany/address_template.html b/erpnext/regional/germany/address_template.html
index 0df7867..7fa4c32 100644
--- a/erpnext/regional/germany/address_template.html
+++ b/erpnext/regional/germany/address_template.html
@@ -1,8 +1,8 @@
{{ address_line1 }}<br>
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
-{{ pincode }} {{ city }}<br>
-{% if country %}{{ country }}<br>{% endif -%}
-<br>
-{% if phone %}Tel: {{ phone }}<br>{% endif -%}
-{% if fax %}Fax: {{ fax }}<br>{% endif -%}
-{% if email_id %}E-Mail: {{ email_id }}<br>{% endif -%}
+{% if country in ["Germany", "Deutschland"] %}
+ {{ pincode }} {{ city }}
+{% else %}
+ {{ pincode }} {{ city | upper }}<br>
+ {{ country | upper }}
+{% endif %}
diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py
index 46c874b..0ed98b7 100644
--- a/erpnext/regional/india/__init__.py
+++ b/erpnext/regional/india/__init__.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+from six import iteritems
states = [
'',
@@ -79,4 +80,6 @@
"Uttar Pradesh": "09",
"Uttarakhand": "05",
"West Bengal": "19",
-}
\ No newline at end of file
+}
+
+number_state_mapping = {v: k for k, v in iteritems(state_numbers)}
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 756c17d..14fdba0 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -107,7 +107,12 @@
dict(fieldname='gst_category', label='GST Category',
fieldtype='Select', insert_after='gst_section', print_hide=1,
options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders',
- fetch_from='supplier.gst_category', fetch_if_empty=1)
+ fetch_from='supplier.gst_category', fetch_if_empty=1),
+ dict(fieldname='export_type', label='Export Type',
+ fieldtype='Select', insert_after='gst_category', print_hide=1,
+ depends_on='eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
+ options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='supplier.export_type',
+ fetch_if_empty=1),
]
sales_invoice_gst_category = [
@@ -116,20 +121,21 @@
dict(fieldname='gst_category', label='GST Category',
fieldtype='Select', insert_after='gst_section', print_hide=1,
options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
- fetch_from='customer.gst_category', fetch_if_empty=1)
+ fetch_from='customer.gst_category', fetch_if_empty=1),
+ dict(fieldname='export_type', label='Export Type',
+ fieldtype='Select', insert_after='gst_category', print_hide=1,
+ depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
+ options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='customer.export_type',
+ fetch_if_empty=1),
]
invoice_gst_fields = [
dict(fieldname='invoice_copy', label='Invoice Copy',
- fieldtype='Select', insert_after='gst_category', print_hide=1, allow_on_submit=1,
+ fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1,
options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'),
dict(fieldname='reverse_charge', label='Reverse Charge',
fieldtype='Select', insert_after='invoice_copy', print_hide=1,
options='Y\nN', default='N'),
- dict(fieldname='export_type', label='Export Type',
- fieldtype='Select', insert_after='reverse_charge', print_hide=1,
- depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
- options='\nWith Payment of Tax\nWithout Payment of Tax'),
dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN',
fieldtype='Data', insert_after='export_type', print_hide=1),
dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='ecommerce_gstin'),
@@ -142,13 +148,13 @@
purchase_invoice_gst_fields = [
dict(fieldname='supplier_gstin', label='Supplier GSTIN',
fieldtype='Data', insert_after='supplier_address',
- fetch_from='supplier_address.gstin', print_hide=1),
+ fetch_from='supplier_address.gstin', print_hide=1, read_only=1),
dict(fieldname='company_gstin', label='Company GSTIN',
fieldtype='Data', insert_after='shipping_address_display',
- fetch_from='shipping_address.gstin', print_hide=1),
+ fetch_from='shipping_address.gstin', print_hide=1, read_only=1),
dict(fieldname='place_of_supply', label='Place of Supply',
fieldtype='Data', insert_after='shipping_address',
- print_hide=1, read_only=0),
+ print_hide=1, read_only=1),
]
purchase_invoice_itc_fields = [
@@ -167,17 +173,17 @@
sales_invoice_gst_fields = [
dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
- fieldtype='Data', insert_after='customer_address',
+ fieldtype='Data', insert_after='customer_address', read_only=1,
fetch_from='customer_address.gstin', print_hide=1),
dict(fieldname='customer_gstin', label='Customer GSTIN',
fieldtype='Data', insert_after='shipping_address_name',
fetch_from='shipping_address_name.gstin', print_hide=1),
dict(fieldname='place_of_supply', label='Place of Supply',
fieldtype='Data', insert_after='customer_gstin',
- print_hide=1, read_only=0),
+ print_hide=1, read_only=1),
dict(fieldname='company_gstin', label='Company GSTIN',
fieldtype='Data', insert_after='company_address',
- fetch_from='company_address.gstin', print_hide=1),
+ fetch_from='company_address.gstin', print_hide=1, read_only=1),
]
sales_invoice_shipping_fields = [
@@ -194,7 +200,11 @@
inter_state_gst_field = [
dict(fieldname='is_inter_state', label='Is Inter State',
- fieldtype='Check', insert_after='disabled', print_hide=1)
+ fieldtype='Check', insert_after='disabled', print_hide=1),
+ dict(fieldname='tax_category_column_break', fieldtype='Column Break',
+ insert_after='is_inter_state'),
+ dict(fieldname='gst_state', label='Source State', fieldtype='Select',
+ options='\n'.join(states), insert_after='company')
]
ewaybill_fields = [
@@ -374,8 +384,7 @@
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
'Sales Order': sales_invoice_gst_fields,
- 'Sales Taxes and Charges Template': inter_state_gst_field,
- 'Purchase Taxes and Charges Template': inter_state_gst_field,
+ 'Tax Category': inter_state_gst_field,
'Item': [
dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Link', options='GST HSN Code', insert_after='item_group'),
@@ -459,6 +468,15 @@
'insert_after': 'gst_transporter_id',
'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders',
'default': 'Unregistered'
+ },
+ {
+ 'fieldname': 'export_type',
+ 'label': 'Export Type',
+ 'fieldtype': 'Select',
+ 'insert_after': 'gst_category',
+ 'default': 'Without Payment of Tax',
+ 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
+ 'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
],
'Customer': [
@@ -469,6 +487,15 @@
'insert_after': 'customer_type',
'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
'default': 'Unregistered'
+ },
+ {
+ 'fieldname': 'export_type',
+ 'label': 'Export Type',
+ 'fieldtype': 'Select',
+ 'insert_after': 'gst_category',
+ 'default': 'Without Payment of Tax',
+ 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
+ 'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
]
}
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
new file mode 100644
index 0000000..1e59032
--- /dev/null
+++ b/erpnext/regional/india/taxes.js
@@ -0,0 +1,41 @@
+erpnext.setup_auto_gst_taxation = (doctype) => {
+ frappe.ui.form.on(doctype, {
+ company_address: function(frm) {
+ frm.trigger('get_tax_template');
+ },
+ shipping_address: function(frm) {
+ frm.trigger('get_tax_template');
+ },
+ tax_category: function(frm) {
+ frm.trigger('get_tax_template');
+ },
+ get_tax_template: function(frm) {
+ let party_details = {
+ 'shipping_address': frm.doc.shipping_address || '',
+ 'shipping_address_name': frm.doc.shipping_address_name || '',
+ 'customer_address': frm.doc.customer_address || '',
+ 'customer': frm.doc.customer,
+ 'supplier': frm.doc.supplier,
+ 'supplier_gstin': frm.doc.supplier_gstin,
+ 'company_gstin': frm.doc.company_gstin,
+ 'tax_category': frm.doc.tax_category
+ };
+
+ frappe.call({
+ method: 'erpnext.regional.india.utils.get_regional_address_details',
+ args: {
+ party_details: JSON.stringify(party_details),
+ doctype: frm.doc.doctype,
+ company: frm.doc.company,
+ return_taxes: 1
+ },
+ callback: function(r) {
+ if(r.message) {
+ frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
+ }
+ }
+ });
+ }
+ });
+};
+
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index aae0779..0f9156a 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -7,6 +7,8 @@
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.hr.utils import get_salary_assignment
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
+from erpnext.regional.india import number_state_mapping
+from six import string_types
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -46,6 +48,14 @@
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
+def update_gst_category(doc, method):
+ for link in doc.links:
+ if link.link_doctype in ['Customer', 'Supplier']:
+ if doc.get('gstin'):
+ frappe.db.sql("""
+ UPDATE `tab{0}` SET gst_category = %s WHERE name = %s AND gst_category = 'Unregistered'
+ """.format(link.link_doctype), ("Registered Regular", link.link_name)) #nosec
+
def set_gst_state_and_state_number(doc):
if not doc.gst_state:
if not doc.state:
@@ -122,44 +132,108 @@
'''test function'''
return 'overridden'
-def get_place_of_supply(out, doctype):
+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"):
- address_name = out.shipping_address_name or out.customer_address
- elif doctype == "Purchase Invoice":
- address_name = out.shipping_address or out.supplier_address
+ if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
+ address_name = party_details.shipping_address_name or party_details.customer_address
+ elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
+ address_name = party_details.shipping_address or party_details.supplier_address
if address_name:
address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1)
if address and address.gst_state and address.gst_state_number:
return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
-def get_regional_address_details(out, doctype, company):
- out.place_of_supply = get_place_of_supply(out, doctype)
+@frappe.whitelist()
+def get_regional_address_details(party_details, doctype, company, return_taxes=None):
- if not out.place_of_supply: return
+ if isinstance(party_details, string_types):
+ party_details = json.loads(party_details)
+ party_details = frappe._dict(party_details)
- if doctype in ("Sales Invoice", "Delivery Note"):
+ party_details.place_of_supply = get_place_of_supply(party_details, doctype)
+ if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
- if not out.company_gstin:
- return
- elif doctype == "Purchase Invoice":
- master_doctype = "Purchase Taxes and Charges Template"
- if not out.supplier_gstin:
+
+ get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
+ get_tax_template_based_on_category(master_doctype, company, party_details)
+
+ if party_details.get('taxes_and_charges') and return_taxes:
+ return party_details
+
+ if not party_details.company_gstin:
return
- if ((doctype in ("Sales Invoice", "Delivery Note") and out.company_gstin
- and out.company_gstin[:2] != out.place_of_supply[:2]) or (doctype == "Purchase Invoice"
- and out.supplier_gstin and out.supplier_gstin[:2] != out.place_of_supply[:2])):
- default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0})
+ elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
+ master_doctype = "Purchase Taxes and Charges Template"
+
+ get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
+ get_tax_template_based_on_category(master_doctype, company, party_details)
+
+ if party_details.get('taxes_and_charges') and return_taxes:
+ return party_details
+
+ if not party_details.supplier_gstin:
+ return
+
+ if not party_details.place_of_supply: return
+
+ if not party_details.company_gstin: return
+
+ if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
+ and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
+ "Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])):
+ default_tax = get_tax_template(master_doctype, company, 1, party_details.company_gstin[:2])
else:
- default_tax = frappe.db.get_value(master_doctype, {"company": company, "disabled":0, "is_default": 1})
+ default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
if not default_tax:
return
- out["taxes_and_charges"] = default_tax
- out.taxes = get_taxes_and_charges(master_doctype, default_tax)
+ party_details["taxes_and_charges"] = default_tax
+ party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
+
+ if return_taxes:
+ return party_details
+
+def get_tax_template_based_on_category(master_doctype, company, party_details):
+ if not party_details.get('tax_category'):
+ return
+
+ default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')},
+ 'name')
+
+ if default_tax:
+ party_details["taxes_and_charges"] = default_tax
+ party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
+
+def get_tax_template(master_doctype, company, is_inter_state, state_code):
+ tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'],
+ filters = {'is_inter_state': is_inter_state})
+
+ default_tax = ''
+
+ for tax_category in tax_categories:
+ if tax_category.gst_state == number_state_mapping[state_code] or \
+ (not default_tax and not tax_category.gst_state):
+ default_tax = frappe.db.get_value(master_doctype,
+ {'disabled': 0, 'tax_category': tax_category.name}, 'name')
+
+ return default_tax
+
+def get_tax_template_for_sez(party_details, master_doctype, company, party_type):
+
+ gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))},
+ ['gst_category', 'export_type'], as_dict=1)
+
+ if gst_details:
+ if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax':
+ default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0,
+ "gst_state": number_state_mapping[party_details.company_gstin[:2]]})
+
+ party_details["taxes_and_charges"] = default_tax
+ party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
+
def calculate_annual_eligible_hra_exemption(doc):
basic_component = frappe.get_cached_value('Company', doc.company, "basic_component")
@@ -555,7 +629,7 @@
filters={"parent": "GST Settings", "company": company},
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
- if not gst_settings_accounts:
+ if not gst_settings_accounts and not frappe.flags.in_test:
frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts:
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index ee8735f..bd70639 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -10,17 +10,26 @@
from __future__ import unicode_literals
import datetime
import json
+import zlib
+import zipfile
+import six
+from six import BytesIO
from six import string_types
import frappe
from frappe import _
import pandas as pd
+from .datev_constants import DataCategory
+from .datev_constants import Transactions
+from .datev_constants import DebtorsCreditors
+from .datev_constants import AccountNames
+from .datev_constants import QUERY_REPORT_COLUMNS
def execute(filters=None):
"""Entry point for frappe."""
validate(filters)
- result = get_gl_entries(filters, as_dict=0)
- columns = get_columns()
+ result = get_transactions(filters, as_dict=0)
+ columns = QUERY_REPORT_COLUMNS
return columns, result
@@ -41,65 +50,8 @@
except frappe.DoesNotExistError:
frappe.throw(_('Please create <b>DATEV Settings</b> for Company <b>{}</b>.').format(filters.get('company')))
-def get_columns():
- """Return the list of columns that will be shown in query report."""
- columns = [
- {
- "label": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldtype": "Currency",
- },
- {
- "label": "Soll/Haben-Kennzeichen",
- "fieldname": "Soll/Haben-Kennzeichen",
- "fieldtype": "Data",
- },
- {
- "label": "Kontonummer",
- "fieldname": "Kontonummer",
- "fieldtype": "Data",
- },
- {
- "label": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldtype": "Data",
- },
- {
- "label": "Belegdatum",
- "fieldname": "Belegdatum",
- "fieldtype": "Date",
- },
- {
- "label": "Buchungstext",
- "fieldname": "Buchungstext",
- "fieldtype": "Text",
- },
- {
- "label": "Beleginfo - Art 1",
- "fieldname": "Beleginfo - Art 1",
- "fieldtype": "Data",
- },
- {
- "label": "Beleginfo - Inhalt 1",
- "fieldname": "Beleginfo - Inhalt 1",
- "fieldtype": "Data",
- },
- {
- "label": "Beleginfo - Art 2",
- "fieldname": "Beleginfo - Art 2",
- "fieldtype": "Data",
- },
- {
- "label": "Beleginfo - Inhalt 2",
- "fieldname": "Beleginfo - Inhalt 2",
- "fieldtype": "Data",
- }
- ]
- return columns
-
-
-def get_gl_entries(filters, as_dict):
+def get_transactions(filters, as_dict=1):
"""
Get a list of accounting entries.
@@ -111,7 +63,7 @@
as_dict -- return as list of dicts [0,1]
"""
gl_entries = frappe.db.sql("""
- select
+ SELECT
/* either debit or credit amount; always positive */
case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)',
@@ -132,7 +84,7 @@
gl.against_voucher_type as 'Beleginfo - Art 2',
gl.against_voucher as 'Beleginfo - Inhalt 2'
- from `tabGL Entry` gl
+ FROM `tabGL Entry` gl
/* Statistisches Konto (Debitoren/Kreditoren) */
left join `tabParty Account` pa
@@ -155,15 +107,127 @@
left join `tabAccount` acc_against_pa
on pa.account = acc_against_pa.name
- where gl.company = %(company)s
- and DATE(gl.posting_date) >= %(from_date)s
- and DATE(gl.posting_date) <= %(to_date)s
- order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict)
+ WHERE gl.company = %(company)s
+ AND DATE(gl.posting_date) >= %(from_date)s
+ AND DATE(gl.posting_date) <= %(to_date)s
+ ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1)
return gl_entries
-def get_datev_csv(data, filters):
+def get_customers(filters):
+ """
+ Get a list of Customers.
+
+ Arguments:
+ filters -- dict of filters to be passed to the sql query
+ """
+ return frappe.db.sql("""
+ SELECT
+
+ acc.account_number as 'Konto',
+ cus.customer_name as 'Name (Adressatentyp Unternehmen)',
+ case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp',
+ adr.address_line1 as 'Straße',
+ adr.pincode as 'Postleitzahl',
+ adr.city as 'Ort',
+ UPPER(country.code) as 'Land',
+ adr.address_line2 as 'Adresszusatz',
+ con.email_id as 'E-Mail',
+ coalesce(con.mobile_no, con.phone) as 'Telefon',
+ cus.website as 'Internet',
+ cus.tax_id as 'Steuernummer',
+ ccl.credit_limit as 'Kreditlimit (Debitor)'
+
+ FROM `tabParty Account` par
+
+ left join `tabAccount` acc
+ on acc.name = par.account
+
+ left join `tabCustomer` cus
+ on cus.name = par.parent
+
+ left join `tabAddress` adr
+ on adr.name = cus.customer_primary_address
+
+ left join `tabCountry` country
+ on country.name = adr.country
+
+ left join `tabContact` con
+ on con.name = cus.customer_primary_contact
+
+ left join `tabCustomer Credit Limit` ccl
+ on ccl.parent = cus.name
+ and ccl.company = par.company
+
+ WHERE par.company = %(company)s
+ AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1)
+
+
+def get_suppliers(filters):
+ """
+ Get a list of Suppliers.
+
+ Arguments:
+ filters -- dict of filters to be passed to the sql query
+ """
+ return frappe.db.sql("""
+ SELECT
+
+ acc.account_number as 'Konto',
+ sup.supplier_name as 'Name (Adressatentyp Unternehmen)',
+ case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp',
+ adr.address_line1 as 'Straße',
+ adr.pincode as 'Postleitzahl',
+ adr.city as 'Ort',
+ UPPER(country.code) as 'Land',
+ adr.address_line2 as 'Adresszusatz',
+ con.email_id as 'E-Mail',
+ coalesce(con.mobile_no, con.phone) as 'Telefon',
+ sup.website as 'Internet',
+ sup.tax_id as 'Steuernummer',
+ case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
+
+ FROM `tabParty Account` par
+
+ left join `tabAccount` acc
+ on acc.name = par.account
+
+ left join `tabSupplier` sup
+ on sup.name = par.parent
+
+ left join `tabDynamic Link` dyn_adr
+ on dyn_adr.link_name = sup.name
+ and dyn_adr.link_doctype = 'Supplier'
+ and dyn_adr.parenttype = 'Address'
+
+ left join `tabAddress` adr
+ on adr.name = dyn_adr.parent
+ and adr.is_primary_address = '1'
+
+ left join `tabCountry` country
+ on country.name = adr.country
+
+ left join `tabDynamic Link` dyn_con
+ on dyn_con.link_name = sup.name
+ and dyn_con.link_doctype = 'Supplier'
+ and dyn_con.parenttype = 'Contact'
+
+ left join `tabContact` con
+ on con.name = dyn_con.parent
+ and con.is_primary_contact = '1'
+
+ WHERE par.company = %(company)s
+ AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1)
+
+
+def get_account_names(filters):
+ return frappe.get_list("Account",
+ fields=["account_number as Konto", "name as Kontenbeschriftung"],
+ filters={"company": filters.get("company"), "is_group": "0"})
+
+
+def get_datev_csv(data, filters, csv_class):
"""
Fill in missing columns and return a CSV in DATEV Format.
@@ -174,7 +238,46 @@
Arguments:
data -- array of dictionaries
filters -- dict
+ csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
"""
+ header = get_header(filters, csv_class)
+
+ empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
+ data_df = pd.DataFrame.from_records(data)
+
+ result = empty_df.append(data_df, sort=True)
+
+ if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
+ result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
+
+ if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
+ result['Sprach-ID'] = 'de-DE'
+
+ header = ';'.join(header).encode('latin_1')
+ data = result.to_csv(
+ # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
+ sep=str(';'),
+ # European decimal seperator
+ decimal=',',
+ # Windows "ANSI" encoding
+ encoding='latin_1',
+ # format date as DDMM
+ date_format='%d%m',
+ # Windows line terminator
+ line_terminator='\r\n',
+ # Do not number rows
+ index=False,
+ # Use all columns defined above
+ columns=csv_class.COLUMNS
+ )
+
+ if not six.PY2:
+ data = data.encode('latin_1')
+
+ return header + b'\r\n' + data
+
+
+def get_header(filters, csv_class):
header = [
# A = DATEV format
# DTVF = created by DATEV software,
@@ -185,18 +288,8 @@
# 510 = 5.10,
# 720 = 7.20
"510",
- # C = Data category
- # 21 = Transaction batch (Buchungsstapel),
- # 67 = Buchungstextkonstanten,
- # 16 = Debitors/Creditors,
- # 20 = Account names (Kontenbeschriftungen)
- "21",
- # D = Format name
- # Buchungsstapel,
- # Buchungstextkonstanten,
- # Debitoren/Kreditoren,
- # Kontenbeschriftungen
- "Buchungsstapel",
+ csv_class.DATA_CATEGORY,
+ csv_class.FORMAT_NAME,
# E = Format version (regarding format name)
"",
# F = Generated on
@@ -224,16 +317,17 @@
# P = Transaction batch end date (YYYYMMDD)
frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"),
# Q = Description (for example, "January - February 2019 Transactions")
- "{} - {} Buchungsstapel".format(
- frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"),
- frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy")
+ "{} - {} {}".format(
+ frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"),
+ frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy"),
+ csv_class.FORMAT_NAME
),
# R = Diktatkürzel
"",
# S = Buchungstyp
# 1 = Transaction batch (Buchungsstapel),
# 2 = Annual financial statement (Jahresabschluss)
- "1",
+ "1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
# T = Rechnungslegungszweck
"",
# U = Festschreibung
@@ -241,185 +335,8 @@
# V = Kontoführungs-Währungskennzeichen des Geldkontos
frappe.get_value("Company", filters.get("company"), "default_currency")
]
- columns = [
- # All possible columns must tbe listed here, because DATEV requires them to
- # be present in the CSV.
- # ---
- # Umsatz
- "Umsatz (ohne Soll/Haben-Kz)",
- "Soll/Haben-Kennzeichen",
- "WKZ Umsatz",
- "Kurs",
- "Basis-Umsatz",
- "WKZ Basis-Umsatz",
- # Konto/Gegenkonto
- "Kontonummer",
- "Gegenkonto (ohne BU-Schlüssel)",
- "BU-Schlüssel",
- # Datum
- "Belegdatum",
- # Belegfelder
- "Belegfeld 1",
- "Belegfeld 2",
- # Weitere Felder
- "Skonto",
- "Buchungstext",
- # OPOS-Informationen
- "Postensperre",
- "Diverse Adressnummer",
- "Geschäftspartnerbank",
- "Sachverhalt",
- "Zinssperre",
- # Digitaler Beleg
- "Beleglink",
- # Beleginfo
- "Beleginfo - Art 1",
- "Beleginfo - Inhalt 1",
- "Beleginfo - Art 2",
- "Beleginfo - Inhalt 2",
- "Beleginfo - Art 3",
- "Beleginfo - Inhalt 3",
- "Beleginfo - Art 4",
- "Beleginfo - Inhalt 4",
- "Beleginfo - Art 5",
- "Beleginfo - Inhalt 5",
- "Beleginfo - Art 6",
- "Beleginfo - Inhalt 6",
- "Beleginfo - Art 7",
- "Beleginfo - Inhalt 7",
- "Beleginfo - Art 8",
- "Beleginfo - Inhalt 8",
- # Kostenrechnung
- "Kost 1 - Kostenstelle",
- "Kost 2 - Kostenstelle",
- "Kost-Menge",
- # Steuerrechnung
- "EU-Land u. UStID",
- "EU-Steuersatz",
- "Abw. Versteuerungsart",
- # L+L Sachverhalt
- "Sachverhalt L+L",
- "Funktionsergänzung L+L",
- # Funktion Steuerschlüssel 49
- "BU 49 Hauptfunktionstyp",
- "BU 49 Hauptfunktionsnummer",
- "BU 49 Funktionsergänzung",
- # Zusatzinformationen
- "Zusatzinformation - Art 1",
- "Zusatzinformation - Inhalt 1",
- "Zusatzinformation - Art 2",
- "Zusatzinformation - Inhalt 2",
- "Zusatzinformation - Art 3",
- "Zusatzinformation - Inhalt 3",
- "Zusatzinformation - Art 4",
- "Zusatzinformation - Inhalt 4",
- "Zusatzinformation - Art 5",
- "Zusatzinformation - Inhalt 5",
- "Zusatzinformation - Art 6",
- "Zusatzinformation - Inhalt 6",
- "Zusatzinformation - Art 7",
- "Zusatzinformation - Inhalt 7",
- "Zusatzinformation - Art 8",
- "Zusatzinformation - Inhalt 8",
- "Zusatzinformation - Art 9",
- "Zusatzinformation - Inhalt 9",
- "Zusatzinformation - Art 10",
- "Zusatzinformation - Inhalt 10",
- "Zusatzinformation - Art 11",
- "Zusatzinformation - Inhalt 11",
- "Zusatzinformation - Art 12",
- "Zusatzinformation - Inhalt 12",
- "Zusatzinformation - Art 13",
- "Zusatzinformation - Inhalt 13",
- "Zusatzinformation - Art 14",
- "Zusatzinformation - Inhalt 14",
- "Zusatzinformation - Art 15",
- "Zusatzinformation - Inhalt 15",
- "Zusatzinformation - Art 16",
- "Zusatzinformation - Inhalt 16",
- "Zusatzinformation - Art 17",
- "Zusatzinformation - Inhalt 17",
- "Zusatzinformation - Art 18",
- "Zusatzinformation - Inhalt 18",
- "Zusatzinformation - Art 19",
- "Zusatzinformation - Inhalt 19",
- "Zusatzinformation - Art 20",
- "Zusatzinformation - Inhalt 20",
- # Mengenfelder LuF
- "Stück",
- "Gewicht",
- # Forderungsart
- "Zahlweise",
- "Forderungsart",
- "Veranlagungsjahr",
- "Zugeordnete Fälligkeit",
- # Weitere Felder
- "Skontotyp",
- # Anzahlungen
- "Auftragsnummer",
- "Buchungstyp",
- "USt-Schlüssel (Anzahlungen)",
- "EU-Land (Anzahlungen)",
- "Sachverhalt L+L (Anzahlungen)",
- "EU-Steuersatz (Anzahlungen)",
- "Erlöskonto (Anzahlungen)",
- # Stapelinformationen
- "Herkunft-Kz",
- # Technische Identifikation
- "Buchungs GUID",
- # Kostenrechnung
- "Kost-Datum",
- # OPOS-Informationen
- "SEPA-Mandatsreferenz",
- "Skontosperre",
- # Gesellschafter und Sonderbilanzsachverhalt
- "Gesellschaftername",
- "Beteiligtennummer",
- "Identifikationsnummer",
- "Zeichnernummer",
- # OPOS-Informationen
- "Postensperre bis",
- # Gesellschafter und Sonderbilanzsachverhalt
- "Bezeichnung SoBil-Sachverhalt",
- "Kennzeichen SoBil-Buchung",
- # Stapelinformationen
- "Festschreibung",
- # Datum
- "Leistungsdatum",
- "Datum Zuord. Steuerperiode",
- # OPOS-Informationen
- "Fälligkeit",
- # Konto/Gegenkonto
- "Generalumkehr (GU)",
- # Steuersatz für Steuerschlüssel
- "Steuersatz",
- "Land"
- ]
+ return header
- empty_df = pd.DataFrame(columns=columns)
- data_df = pd.DataFrame.from_records(data)
-
- result = empty_df.append(data_df)
- result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
-
- header = ';'.join(header).encode('latin_1')
- data = result.to_csv(
- sep=b';',
- # European decimal seperator
- decimal=',',
- # Windows "ANSI" encoding
- encoding='latin_1',
- # format date as DDMM
- date_format='%d%m',
- # Windows line terminator
- line_terminator=b'\r\n',
- # Do not number rows
- index=False,
- # Use all columns defined above
- columns=columns
- )
-
- return header + b'\r\n' + data
@frappe.whitelist()
def download_datev_csv(filters=None):
@@ -438,8 +355,31 @@
filters = json.loads(filters)
validate(filters)
- data = get_gl_entries(filters, as_dict=1)
- frappe.response['result'] = get_datev_csv(data, filters)
- frappe.response['doctype'] = 'EXTF_Buchungsstapel'
- frappe.response['type'] = 'csv'
+ # This is where my zip will be written
+ zip_buffer = BytesIO()
+ # This is my zip file
+ datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
+
+ transactions = get_transactions(filters)
+ transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions)
+ datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv)
+
+ account_names = get_account_names(filters)
+ account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames)
+ datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv)
+
+ customers = get_customers(filters)
+ customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
+ datev_zip.writestr('EXTF_Kunden.csv', customers_csv)
+
+ suppliers = get_suppliers(filters)
+ suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
+ datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv)
+
+ # You must call close() before exiting your program or essential records will not be written.
+ datev_zip.close()
+
+ frappe.response['filecontent'] = zip_buffer.getvalue()
+ frappe.response['filename'] = 'DATEV.zip'
+ frappe.response['type'] = 'binary'
diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py
new file mode 100644
index 0000000..1c9bd23
--- /dev/null
+++ b/erpnext/regional/report/datev/datev_constants.py
@@ -0,0 +1,512 @@
+# coding: utf-8
+"""Constants used in datev.py."""
+
+TRANSACTION_COLUMNS = [
+ # All possible columns must tbe listed here, because DATEV requires them to
+ # be present in the CSV.
+ # ---
+ # Umsatz
+ "Umsatz (ohne Soll/Haben-Kz)",
+ "Soll/Haben-Kennzeichen",
+ "WKZ Umsatz",
+ "Kurs",
+ "Basis-Umsatz",
+ "WKZ Basis-Umsatz",
+ # Konto/Gegenkonto
+ "Kontonummer",
+ "Gegenkonto (ohne BU-Schlüssel)",
+ "BU-Schlüssel",
+ # Datum
+ "Belegdatum",
+ # Belegfelder
+ "Belegfeld 1",
+ "Belegfeld 2",
+ # Weitere Felder
+ "Skonto",
+ "Buchungstext",
+ # OPOS-Informationen
+ "Postensperre",
+ "Diverse Adressnummer",
+ "Geschäftspartnerbank",
+ "Sachverhalt",
+ "Zinssperre",
+ # Digitaler Beleg
+ "Beleglink",
+ # Beleginfo
+ "Beleginfo - Art 1",
+ "Beleginfo - Inhalt 1",
+ "Beleginfo - Art 2",
+ "Beleginfo - Inhalt 2",
+ "Beleginfo - Art 3",
+ "Beleginfo - Inhalt 3",
+ "Beleginfo - Art 4",
+ "Beleginfo - Inhalt 4",
+ "Beleginfo - Art 5",
+ "Beleginfo - Inhalt 5",
+ "Beleginfo - Art 6",
+ "Beleginfo - Inhalt 6",
+ "Beleginfo - Art 7",
+ "Beleginfo - Inhalt 7",
+ "Beleginfo - Art 8",
+ "Beleginfo - Inhalt 8",
+ # Kostenrechnung
+ "Kost 1 - Kostenstelle",
+ "Kost 2 - Kostenstelle",
+ "Kost-Menge",
+ # Steuerrechnung
+ "EU-Land u. UStID",
+ "EU-Steuersatz",
+ "Abw. Versteuerungsart",
+ # L+L Sachverhalt
+ "Sachverhalt L+L",
+ "Funktionsergänzung L+L",
+ # Funktion Steuerschlüssel 49
+ "BU 49 Hauptfunktionstyp",
+ "BU 49 Hauptfunktionsnummer",
+ "BU 49 Funktionsergänzung",
+ # Zusatzinformationen
+ "Zusatzinformation - Art 1",
+ "Zusatzinformation - Inhalt 1",
+ "Zusatzinformation - Art 2",
+ "Zusatzinformation - Inhalt 2",
+ "Zusatzinformation - Art 3",
+ "Zusatzinformation - Inhalt 3",
+ "Zusatzinformation - Art 4",
+ "Zusatzinformation - Inhalt 4",
+ "Zusatzinformation - Art 5",
+ "Zusatzinformation - Inhalt 5",
+ "Zusatzinformation - Art 6",
+ "Zusatzinformation - Inhalt 6",
+ "Zusatzinformation - Art 7",
+ "Zusatzinformation - Inhalt 7",
+ "Zusatzinformation - Art 8",
+ "Zusatzinformation - Inhalt 8",
+ "Zusatzinformation - Art 9",
+ "Zusatzinformation - Inhalt 9",
+ "Zusatzinformation - Art 10",
+ "Zusatzinformation - Inhalt 10",
+ "Zusatzinformation - Art 11",
+ "Zusatzinformation - Inhalt 11",
+ "Zusatzinformation - Art 12",
+ "Zusatzinformation - Inhalt 12",
+ "Zusatzinformation - Art 13",
+ "Zusatzinformation - Inhalt 13",
+ "Zusatzinformation - Art 14",
+ "Zusatzinformation - Inhalt 14",
+ "Zusatzinformation - Art 15",
+ "Zusatzinformation - Inhalt 15",
+ "Zusatzinformation - Art 16",
+ "Zusatzinformation - Inhalt 16",
+ "Zusatzinformation - Art 17",
+ "Zusatzinformation - Inhalt 17",
+ "Zusatzinformation - Art 18",
+ "Zusatzinformation - Inhalt 18",
+ "Zusatzinformation - Art 19",
+ "Zusatzinformation - Inhalt 19",
+ "Zusatzinformation - Art 20",
+ "Zusatzinformation - Inhalt 20",
+ # Mengenfelder LuF
+ "Stück",
+ "Gewicht",
+ # Forderungsart
+ "Zahlweise",
+ "Forderungsart",
+ "Veranlagungsjahr",
+ "Zugeordnete Fälligkeit",
+ # Weitere Felder
+ "Skontotyp",
+ # Anzahlungen
+ "Auftragsnummer",
+ "Buchungstyp",
+ "USt-Schlüssel (Anzahlungen)",
+ "EU-Land (Anzahlungen)",
+ "Sachverhalt L+L (Anzahlungen)",
+ "EU-Steuersatz (Anzahlungen)",
+ "Erlöskonto (Anzahlungen)",
+ # Stapelinformationen
+ "Herkunft-Kz",
+ # Technische Identifikation
+ "Buchungs GUID",
+ # Kostenrechnung
+ "Kost-Datum",
+ # OPOS-Informationen
+ "SEPA-Mandatsreferenz",
+ "Skontosperre",
+ # Gesellschafter und Sonderbilanzsachverhalt
+ "Gesellschaftername",
+ "Beteiligtennummer",
+ "Identifikationsnummer",
+ "Zeichnernummer",
+ # OPOS-Informationen
+ "Postensperre bis",
+ # Gesellschafter und Sonderbilanzsachverhalt
+ "Bezeichnung SoBil-Sachverhalt",
+ "Kennzeichen SoBil-Buchung",
+ # Stapelinformationen
+ "Festschreibung",
+ # Datum
+ "Leistungsdatum",
+ "Datum Zuord. Steuerperiode",
+ # OPOS-Informationen
+ "Fälligkeit",
+ # Konto/Gegenkonto
+ "Generalumkehr (GU)",
+ # Steuersatz für Steuerschlüssel
+ "Steuersatz",
+ "Land"
+]
+
+DEBTOR_CREDITOR_COLUMNS = [
+ # All possible columns must tbe listed here, because DATEV requires them to
+ # be present in the CSV.
+ # Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas
+ # ---
+ "Konto",
+ "Name (Adressatentyp Unternehmen)",
+ "Unternehmensgegenstand",
+ "Name (Adressatentyp natürl. Person)",
+ "Vorname (Adressatentyp natürl. Person)",
+ "Name (Adressatentyp keine Angabe)",
+ "Adressatentyp",
+ "Kurzbezeichnung",
+ "EU-Land",
+ "EU-USt-IdNr.",
+ "Anrede",
+ "Titel/Akad. Grad",
+ "Adelstitel",
+ "Namensvorsatz",
+ "Adressart",
+ "Straße",
+ "Postfach",
+ "Postleitzahl",
+ "Ort",
+ "Land",
+ "Versandzusatz",
+ "Adresszusatz",
+ "Abweichende Anrede",
+ "Abw. Zustellbezeichnung 1",
+ "Abw. Zustellbezeichnung 2",
+ "Kennz. Korrespondenzadresse",
+ "Adresse gültig von",
+ "Adresse gültig bis",
+ "Telefon",
+ "Bemerkung (Telefon)",
+ "Telefon Geschäftsleitung",
+ "Bemerkung (Telefon GL)",
+ "E-Mail",
+ "Bemerkung (E-Mail)",
+ "Internet",
+ "Bemerkung (Internet)",
+ "Fax",
+ "Bemerkung (Fax)",
+ "Sonstige",
+ "Bemerkung (Sonstige)",
+ "Bankleitzahl 1",
+ "Bankbezeichnung 1",
+ "Bankkonto-Nummer 1",
+ "Länderkennzeichen 1",
+ "IBAN 1",
+ "Leerfeld 1",
+ "SWIFT-Code 1",
+ "Abw. Kontoinhaber 1",
+ "Kennz. Haupt-Bankverb. 1",
+ "Bankverb. 1 Gültig von",
+ "Bankverb. 1 Gültig bis",
+ "Bankleitzahl 2",
+ "Bankbezeichnung 2",
+ "Bankkonto-Nummer 2",
+ "Länderkennzeichen 2",
+ "IBAN 2",
+ "Leerfeld 2",
+ "SWIFT-Code 2",
+ "Abw. Kontoinhaber 2",
+ "Kennz. Haupt-Bankverb. 2",
+ "Bankverb. 2 gültig von",
+ "Bankverb. 2 gültig bis",
+ "Bankleitzahl 3",
+ "Bankbezeichnung 3",
+ "Bankkonto-Nummer 3",
+ "Länderkennzeichen 3",
+ "IBAN 3",
+ "Leerfeld 3",
+ "SWIFT-Code 3",
+ "Abw. Kontoinhaber 3",
+ "Kennz. Haupt-Bankverb. 3",
+ "Bankverb. 3 gültig von",
+ "Bankverb. 3 gültig bis",
+ "Bankleitzahl 4",
+ "Bankbezeichnung 4",
+ "Bankkonto-Nummer 4",
+ "Länderkennzeichen 4",
+ "IBAN 4",
+ "Leerfeld 4",
+ "SWIFT-Code 4",
+ "Abw. Kontoinhaber 4",
+ "Kennz. Haupt-Bankverb. 4",
+ "Bankverb. 4 Gültig von",
+ "Bankverb. 4 Gültig bis",
+ "Bankleitzahl 5",
+ "Bankbezeichnung 5",
+ "Bankkonto-Nummer 5",
+ "Länderkennzeichen 5",
+ "IBAN 5",
+ "Leerfeld 5",
+ "SWIFT-Code 5",
+ "Abw. Kontoinhaber 5",
+ "Kennz. Haupt-Bankverb. 5",
+ "Bankverb. 5 gültig von",
+ "Bankverb. 5 gültig bis",
+ "Leerfeld 6",
+ "Briefanrede",
+ "Grußformel",
+ "Kundennummer",
+ "Steuernummer",
+ "Sprache",
+ "Ansprechpartner",
+ "Vertreter",
+ "Sachbearbeiter",
+ "Diverse-Konto",
+ "Ausgabeziel",
+ "Währungssteuerung",
+ "Kreditlimit (Debitor)",
+ "Zahlungsbedingung",
+ "Fälligkeit in Tagen (Debitor)",
+ "Skonto in Prozent (Debitor)",
+ "Kreditoren-Ziel 1 (Tage)",
+ "Kreditoren-Skonto 1 (%)",
+ "Kreditoren-Ziel 2 (Tage)",
+ "Kreditoren-Skonto 2 (%)",
+ "Kreditoren-Ziel 3 Brutto (Tage)",
+ "Kreditoren-Ziel 4 (Tage)",
+ "Kreditoren-Skonto 4 (%)",
+ "Kreditoren-Ziel 5 (Tage)",
+ "Kreditoren-Skonto 5 (%)",
+ "Mahnung",
+ "Kontoauszug",
+ "Mahntext 1",
+ "Mahntext 2",
+ "Mahntext 3",
+ "Kontoauszugstext",
+ "Mahnlimit Betrag",
+ "Mahnlimit %",
+ "Zinsberechnung",
+ "Mahnzinssatz 1",
+ "Mahnzinssatz 2",
+ "Mahnzinssatz 3",
+ "Lastschrift",
+ "Verfahren",
+ "Mandantenbank",
+ "Zahlungsträger",
+ "Indiv. Feld 1",
+ "Indiv. Feld 2",
+ "Indiv. Feld 3",
+ "Indiv. Feld 4",
+ "Indiv. Feld 5",
+ "Indiv. Feld 6",
+ "Indiv. Feld 7",
+ "Indiv. Feld 8",
+ "Indiv. Feld 9",
+ "Indiv. Feld 10",
+ "Indiv. Feld 11",
+ "Indiv. Feld 12",
+ "Indiv. Feld 13",
+ "Indiv. Feld 14",
+ "Indiv. Feld 15",
+ "Abweichende Anrede (Rechnungsadresse)",
+ "Adressart (Rechnungsadresse)",
+ "Straße (Rechnungsadresse)",
+ "Postfach (Rechnungsadresse)",
+ "Postleitzahl (Rechnungsadresse)",
+ "Ort (Rechnungsadresse)",
+ "Land (Rechnungsadresse)",
+ "Versandzusatz (Rechnungsadresse)",
+ "Adresszusatz (Rechnungsadresse)",
+ "Abw. Zustellbezeichnung 1 (Rechnungsadresse)",
+ "Abw. Zustellbezeichnung 2 (Rechnungsadresse)",
+ "Adresse Gültig von (Rechnungsadresse)",
+ "Adresse Gültig bis (Rechnungsadresse)",
+ "Bankleitzahl 6",
+ "Bankbezeichnung 6",
+ "Bankkonto-Nummer 6",
+ "Länderkennzeichen 6",
+ "IBAN 6",
+ "Leerfeld 7",
+ "SWIFT-Code 6",
+ "Abw. Kontoinhaber 6",
+ "Kennz. Haupt-Bankverb. 6",
+ "Bankverb 6 gültig von",
+ "Bankverb 6 gültig bis",
+ "Bankleitzahl 7",
+ "Bankbezeichnung 7",
+ "Bankkonto-Nummer 7",
+ "Länderkennzeichen 7",
+ "IBAN 7",
+ "Leerfeld 8",
+ "SWIFT-Code 7",
+ "Abw. Kontoinhaber 7",
+ "Kennz. Haupt-Bankverb. 7",
+ "Bankverb 7 gültig von",
+ "Bankverb 7 gültig bis",
+ "Bankleitzahl 8",
+ "Bankbezeichnung 8",
+ "Bankkonto-Nummer 8",
+ "Länderkennzeichen 8",
+ "IBAN 8",
+ "Leerfeld 9",
+ "SWIFT-Code 8",
+ "Abw. Kontoinhaber 8",
+ "Kennz. Haupt-Bankverb. 8",
+ "Bankverb 8 gültig von",
+ "Bankverb 8 gültig bis",
+ "Bankleitzahl 9",
+ "Bankbezeichnung 9",
+ "Bankkonto-Nummer 9",
+ "Länderkennzeichen 9",
+ "IBAN 9",
+ "Leerfeld 10",
+ "SWIFT-Code 9",
+ "Abw. Kontoinhaber 9",
+ "Kennz. Haupt-Bankverb. 9",
+ "Bankverb 9 gültig von",
+ "Bankverb 9 gültig bis",
+ "Bankleitzahl 10",
+ "Bankbezeichnung 10",
+ "Bankkonto-Nummer 10",
+ "Länderkennzeichen 10",
+ "IBAN 10",
+ "Leerfeld 11",
+ "SWIFT-Code 10",
+ "Abw. Kontoinhaber 10",
+ "Kennz. Haupt-Bankverb. 10",
+ "Bankverb 10 gültig von",
+ "Bankverb 10 gültig bis",
+ "Nummer Fremdsystem",
+ "Insolvent",
+ "SEPA-Mandatsreferenz 1",
+ "SEPA-Mandatsreferenz 2",
+ "SEPA-Mandatsreferenz 3",
+ "SEPA-Mandatsreferenz 4",
+ "SEPA-Mandatsreferenz 5",
+ "SEPA-Mandatsreferenz 6",
+ "SEPA-Mandatsreferenz 7",
+ "SEPA-Mandatsreferenz 8",
+ "SEPA-Mandatsreferenz 9",
+ "SEPA-Mandatsreferenz 10",
+ "Verknüpftes OPOS-Konto",
+ "Mahnsperre bis",
+ "Lastschriftsperre bis",
+ "Zahlungssperre bis",
+ "Gebührenberechnung",
+ "Mahngebühr 1",
+ "Mahngebühr 2",
+ "Mahngebühr 3",
+ "Pauschalberechnung",
+ "Verzugspauschale 1",
+ "Verzugspauschale 2",
+ "Verzugspauschale 3",
+ "Alternativer Suchname",
+ "Status",
+ "Anschrift manuell geändert (Korrespondenzadresse)",
+ "Anschrift individuell (Korrespondenzadresse)",
+ "Anschrift manuell geändert (Rechnungsadresse)",
+ "Anschrift individuell (Rechnungsadresse)",
+ "Fristberechnung bei Debitor",
+ "Mahnfrist 1",
+ "Mahnfrist 2",
+ "Mahnfrist 3",
+ "Letzte Frist"
+]
+
+ACCOUNT_NAME_COLUMNS = [
+ # Account number
+ "Konto",
+ # Account name
+ "Kontenbeschriftung",
+ # Language of the account name
+ # "de-DE" or "en-GB"
+ "Sprach-ID"
+]
+
+QUERY_REPORT_COLUMNS = [
+ {
+ "label": "Umsatz (ohne Soll/Haben-Kz)",
+ "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
+ "fieldtype": "Currency",
+ },
+ {
+ "label": "Soll/Haben-Kennzeichen",
+ "fieldname": "Soll/Haben-Kennzeichen",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Kontonummer",
+ "fieldname": "Kontonummer",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Gegenkonto (ohne BU-Schlüssel)",
+ "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Belegdatum",
+ "fieldname": "Belegdatum",
+ "fieldtype": "Date",
+ },
+ {
+ "label": "Buchungstext",
+ "fieldname": "Buchungstext",
+ "fieldtype": "Text",
+ },
+ {
+ "label": "Beleginfo - Art 1",
+ "fieldname": "Beleginfo - Art 1",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Beleginfo - Inhalt 1",
+ "fieldname": "Beleginfo - Inhalt 1",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Beleginfo - Art 2",
+ "fieldname": "Beleginfo - Art 2",
+ "fieldtype": "Data",
+ },
+ {
+ "label": "Beleginfo - Inhalt 2",
+ "fieldname": "Beleginfo - Inhalt 2",
+ "fieldtype": "Data",
+ }
+]
+
+class DataCategory():
+ """Field of the CSV Header."""
+
+ DEBTORS_CREDITORS = "16"
+ ACCOUNT_NAMES = "20"
+ TRANSACTIONS = "21"
+ POSTING_TEXT_CONSTANTS = "67"
+
+class FormatName():
+ """Field of the CSV Header, corresponds to DataCategory."""
+
+ DEBTORS_CREDITORS = "Debitoren/Kreditoren"
+ ACCOUNT_NAMES = "Kontenbeschriftungen"
+ TRANSACTIONS = "Buchungsstapel"
+ POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten"
+
+class Transactions():
+ DATA_CATEGORY = DataCategory.TRANSACTIONS
+ FORMAT_NAME = FormatName.TRANSACTIONS
+ COLUMNS = TRANSACTION_COLUMNS
+
+class DebtorsCreditors():
+ DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS
+ FORMAT_NAME = FormatName.DEBTORS_CREDITORS
+ COLUMNS = DEBTOR_CREDITOR_COLUMNS
+
+class AccountNames():
+ DATA_CATEGORY = DataCategory.ACCOUNT_NAMES
+ FORMAT_NAME = FormatName.ACCOUNT_NAMES
+ COLUMNS = ACCOUNT_NAME_COLUMNS
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
new file mode 100644
index 0000000..3cc65fe
--- /dev/null
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -0,0 +1,244 @@
+# coding=utf-8
+from __future__ import unicode_literals
+
+import os
+import json
+import zipfile
+from six import BytesIO
+from unittest import TestCase
+
+import frappe
+from frappe.utils import getdate, today, now_datetime, cstr
+from frappe.test_runner import make_test_objects
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
+
+from erpnext.regional.report.datev.datev import validate
+from erpnext.regional.report.datev.datev import get_transactions
+from erpnext.regional.report.datev.datev import get_customers
+from erpnext.regional.report.datev.datev import get_suppliers
+from erpnext.regional.report.datev.datev import get_account_names
+from erpnext.regional.report.datev.datev import get_datev_csv
+from erpnext.regional.report.datev.datev import get_header
+from erpnext.regional.report.datev.datev import download_datev_csv
+
+from erpnext.regional.report.datev.datev_constants import DataCategory
+from erpnext.regional.report.datev.datev_constants import Transactions
+from erpnext.regional.report.datev.datev_constants import DebtorsCreditors
+from erpnext.regional.report.datev.datev_constants import AccountNames
+from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS
+
+def make_company(company_name, abbr):
+ if not frappe.db.exists("Company", company_name):
+ company = frappe.get_doc({
+ "doctype": "Company",
+ "company_name": company_name,
+ "abbr": abbr,
+ "default_currency": "EUR",
+ "country": "Germany",
+ "create_chart_of_accounts_based_on": "Standard Template",
+ "chart_of_accounts": "SKR04 mit Kontonummern"
+ })
+ company.insert()
+ else:
+ company = frappe.get_doc("Company", company_name)
+
+ # indempotent
+ company.create_default_warehouses()
+
+ if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
+ company.create_default_cost_center()
+
+ company.save()
+ return company
+
+def setup_fiscal_year():
+ fiscal_year = None
+ year = cstr(now_datetime().year)
+ if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"):
+ try:
+ fiscal_year = frappe.get_doc({
+ "doctype": "Fiscal Year",
+ "year": year,
+ "year_start_date": "{0}-01-01".format(year),
+ "year_end_date": "{0}-12-31".format(year)
+ })
+ fiscal_year.insert()
+ except frappe.NameError:
+ pass
+
+ if fiscal_year:
+ fiscal_year.set_as_default()
+
+def make_customer_with_account(customer_name, company):
+ acc_name = frappe.db.get_value("Account", {
+ "account_name": customer_name,
+ "company": company.name
+ }, "name")
+
+ if not acc_name:
+ acc = frappe.get_doc({
+ "doctype": "Account",
+ "parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG",
+ "account_name": customer_name,
+ "company": company.name,
+ "account_type": "Receivable",
+ "account_number": "10001"
+ })
+ acc.insert()
+ acc_name = acc.name
+
+ if not frappe.db.exists("Customer", customer_name):
+ customer = frappe.get_doc({
+ "doctype": "Customer",
+ "customer_name": customer_name,
+ "customer_type": "Company",
+ "accounts": [{
+ "company": company.name,
+ "account": acc_name
+ }]
+ })
+ customer.insert()
+ else:
+ customer = frappe.get_doc("Customer", customer_name)
+
+ return customer
+
+def make_item(item_code, company):
+ warehouse_name = frappe.db.get_value("Warehouse", {
+ "warehouse_name": "Stores",
+ "company": company.name
+ }, "name")
+
+ if not frappe.db.exists("Item", item_code):
+ item = frappe.get_doc({
+ "doctype": "Item",
+ "item_code": item_code,
+ "item_name": item_code,
+ "description": item_code,
+ "item_group": "All Item Groups",
+ "is_stock_item": 0,
+ "is_purchase_item": 0,
+ "is_customer_provided_item": 0,
+ "item_defaults": [{
+ "default_warehouse": warehouse_name,
+ "company": company.name
+ }]
+ })
+ item.insert()
+ else:
+ item = frappe.get_doc("Item", item_code)
+ return item
+
+def make_datev_settings(company):
+ if not frappe.db.exists("DATEV Settings", company.name):
+ frappe.get_doc({
+ "doctype": "DATEV Settings",
+ "client": company.name,
+ "client_number": "12345",
+ "consultant_number": "67890"
+ }).insert()
+
+
+class TestDatev(TestCase):
+ def setUp(self):
+ self.company = make_company("_Test GmbH", "_TG")
+ self.customer = make_customer_with_account("_Test Kunde GmbH", self.company)
+ self.filters = {
+ "company": self.company.name,
+ "from_date": today(),
+ "to_date": today()
+ }
+
+ make_datev_settings(self.company)
+ item = make_item("_Test Item", self.company)
+ setup_fiscal_year()
+
+ warehouse = frappe.db.get_value("Item Default", {
+ "parent": item.name,
+ "company": self.company.name
+ }, "default_warehouse")
+
+ income_account = frappe.db.get_value("Account", {
+ "account_number": "4200",
+ "company": self.company.name
+ }, "name")
+
+ tax_account = frappe.db.get_value("Account", {
+ "account_number": "3806",
+ "company": self.company.name
+ }, "name")
+
+ si = create_sales_invoice(
+ company=self.company.name,
+ customer=self.customer.name,
+ currency=self.company.default_currency,
+ debit_to=self.customer.accounts[0].account,
+ income_account="4200 - Erlöse - _TG",
+ expense_account="6990 - Herstellungskosten - _TG",
+ cost_center=self.company.cost_center,
+ warehouse=warehouse,
+ item=item.name,
+ do_not_save=1
+ )
+
+ si.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": tax_account,
+ "description": "Umsatzsteuer 19 %",
+ "rate": 19
+ })
+
+ si.save()
+ si.submit()
+
+ def test_columns(self):
+ def is_subset(get_data, allowed_keys):
+ """
+ Validate that the dict contains only allowed keys.
+
+ Params:
+ get_data -- Function that returns a list of dicts.
+ allowed_keys -- List of allowed keys
+ """
+ data = get_data(self.filters)
+ if data == []:
+ # No data and, therefore, no columns is okay
+ return True
+ actual_set = set(data[0].keys())
+ # allowed set must be interpreted as unicode to match the actual set
+ allowed_set = set({frappe.as_unicode(key) for key in allowed_keys})
+ return actual_set.issubset(allowed_set)
+
+ self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS))
+ self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS))
+ self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS))
+ self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS))
+
+ def test_header(self):
+ self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions))
+ self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames))
+ self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors))
+
+ def test_csv(self):
+ test_data = [{
+ "Umsatz (ohne Soll/Haben-Kz)": 100,
+ "Soll/Haben-Kennzeichen": "H",
+ "Kontonummer": "4200",
+ "Gegenkonto (ohne BU-Schlüssel)": "10000",
+ "Belegdatum": today(),
+ "Buchungstext": "No remark",
+ "Beleginfo - Art 1": "Sales Invoice",
+ "Beleginfo - Inhalt 1": "SINV-0001"
+ }]
+ get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions)
+
+ def test_download(self):
+ """Assert that the returned file is a ZIP file."""
+ download_datev_csv(self.filters)
+
+ # zipfile.is_zipfile() expects a file-like object
+ zip_buffer = BytesIO()
+ zip_buffer.write(frappe.response['filecontent'])
+
+ self.assertTrue(zipfile.is_zipfile(zip_buffer))
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 9682768..1a7ff2b 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -52,17 +52,28 @@
],
onload: function (report) {
- report.page.add_inner_button(__("Download as Json"), function () {
+ report.page.add_inner_button(__("Download as JSON"), function () {
var filters = report.get_values();
- const args = {
- cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
- data: report.data,
- report_name: report.report_name,
- filters: filters
- };
-
- open_url_post(frappe.request.url, args);
+ frappe.call({
+ method: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
+ args: {
+ data: report.data,
+ report_name: report.report_name,
+ filters: filters
+ },
+ callback: function(r) {
+ if (r.message) {
+ const args = {
+ cmd: 'erpnext.regional.report.gstr_1.gstr_1.download_json_file',
+ data: r.message.data,
+ report_name: r.message.report_name,
+ report_type: r.message.report_type
+ };
+ open_url_post(frappe.request.url, args);
+ }
+ }
+ });
});
}
}
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 090616b..4f9cc7f 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -532,16 +532,9 @@
self.columns = self.invoice_columns + self.tax_columns + self.other_columns
@frappe.whitelist()
-def get_json():
- data = frappe._dict(frappe.local.form_dict)
-
- del data["cmd"]
- if "csrf_token" in data:
- del data["csrf_token"]
-
- filters = json.loads(data["filters"])
- report_data = json.loads(data["data"])
- report_name = data["report_name"]
+def get_json(filters, report_name, data):
+ filters = json.loads(filters)
+ report_data = json.loads(data)
gstin = get_company_gstin_number(filters["company"])
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
@@ -575,7 +568,11 @@
out = get_export_json(res)
gst_json["exp"] = out
- download_json_file(report_name, filters["type_of_business"], gst_json)
+ return {
+ 'report_name': report_name,
+ 'report_type': filters['type_of_business'],
+ 'data': gst_json
+ }
def get_b2b_json(res, gstin):
inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
@@ -722,11 +719,15 @@
if gstin:
return gstin[0]["gstin"]
else:
- frappe.throw(_("Please set valid GSTIN No. in Company Address"))
+ frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}".format(
+ frappe.bold(company)
+ )))
-def download_json_file(filename, report_type, data):
+@frappe.whitelist()
+def download_json_file():
''' download json content in a file '''
- frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json'
- frappe.response['filecontent'] = json.dumps(data)
+ data = frappe._dict(frappe.local.form_dict)
+ frappe.response['filename'] = frappe.scrub("{0} {1}".format(data['report_name'], data['report_type'])) + '.json'
+ frappe.response['filecontent'] = data['data']
frappe.response['content_type'] = 'application/json'
frappe.response['type'] = 'download'
diff --git a/erpnext/selling/doctype/sales_order/regional/india.js b/erpnext/selling/doctype/sales_order/regional/india.js
new file mode 100644
index 0000000..c11cfcc
--- /dev/null
+++ b/erpnext/selling/doctype/sales_order/regional/india.js
@@ -0,0 +1,3 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Sales Order');
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 85e8143..2dae0d8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -112,7 +112,6 @@
let allow_delivery = false;
if (doc.docstatus==1) {
- this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
if(this.frm.has_perm("submit")) {
if(doc.status === 'On Hold') {
@@ -136,7 +135,7 @@
if(doc.status !== 'Closed') {
if(doc.status !== 'On Hold') {
- allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty))
+ allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty))
&& !this.frm.doc.skip_delivery_note
if (this.frm.has_perm("submit")) {
@@ -148,6 +147,8 @@
}
}
+ this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
+
// delivery note
if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create'));
@@ -202,7 +203,7 @@
}
}
// payment request
- if(flt(doc.per_billed)==0) {
+ if(flt(doc.per_billed)<100) {
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
}
@@ -361,7 +362,7 @@
},
toggle_delivery_date: function() {
- this.frm.fields_dict.items.grid.toggle_reqd("delivery_date",
+ this.frm.fields_dict.items.grid.toggle_reqd("delivery_date",
(this.frm.doc.order_type == "Sales" && !this.frm.doc.skip_delivery_note));
},
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index e97a4ee..2112a41 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -578,8 +578,12 @@
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
- # set company address
- target.update(get_company_address(target.company))
+ if source.company_address:
+ target.update({'company_address': source.company_address})
+ else:
+ # set company address
+ target.update(get_company_address(target.company))
+
if target.company_address:
target.update(get_fetch_values("Delivery Note", 'company_address', target.company_address))
@@ -645,8 +649,12 @@
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
- # set company address
- target.update(get_company_address(target.company))
+ if source.company_address:
+ target.update({'company_address': source.company_address})
+ else:
+ # set company address
+ target.update(get_company_address(target.company))
+
if target.company_address:
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index bd07841..feb6b76 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -12,6 +12,7 @@
from erpnext.controllers.accounts_controller import update_child_qty_rate
import json
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
+from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
class TestSalesOrder(unittest.TestCase):
def tearDown(self):
@@ -819,6 +820,25 @@
mr_doc = frappe.get_doc('Material Request',mr.get('name'))
self.assertEqual(mr_doc.items[0].sales_order, so.name)
+ def test_so_optional_blanket_order(self):
+ """
+ Expected result: Blanket order Ordered Quantity should only be affected on Sales Order with against_blanket_order = 1.
+ Second Sales Order should not add on to Blanket Orders Ordered Quantity.
+ """
+
+ bo = make_blanket_order(blanket_order_type = "Selling", quantity = 10, rate = 10)
+
+ so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1)
+ so_doc = frappe.get_doc('Sales Order', so.get('name'))
+ # To test if the SO has a Blanket Order
+ self.assertTrue(so_doc.items[0].blanket_order)
+
+ so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0)
+ so_doc = frappe.get_doc('Sales Order', so.get('name'))
+ # To test if the SO does NOT have a Blanket Order
+ self.assertEqual(so_doc.items[0].blanket_order, None)
+
+
def make_sales_order(**args):
so = frappe.new_doc("Sales Order")
args = frappe._dict(args)
@@ -845,7 +865,8 @@
"warehouse": args.warehouse,
"qty": args.qty or 10,
"uom": args.uom or None,
- "rate": args.rate or 100
+ "rate": args.rate or 100,
+ "against_blanket_order": args.against_blanket_order
})
so.delivery_date = add_days(so.transaction_date, 10)
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 3fd1e64..86b09c2 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -68,6 +68,7 @@
"target_warehouse",
"prevdoc_docname",
"col_break4",
+ "against_blanket_order",
"blanket_order",
"blanket_order_rate",
"planning_section",
@@ -574,6 +575,7 @@
"report_hide": 1
},
{
+ "depends_on": "eval:doc.against_blanket_order",
"fieldname": "blanket_order",
"fieldtype": "Link",
"label": "Blanket Order",
@@ -581,6 +583,7 @@
"options": "Blanket Order"
},
{
+ "depends_on": "eval:doc.against_blanket_order",
"fieldname": "blanket_order_rate",
"fieldtype": "Currency",
"label": "Blanket Order Rate",
@@ -741,11 +744,17 @@
"fieldname": "image_section",
"fieldtype": "Section Break",
"label": "Image"
+ },
+ {
+ "default": "0",
+ "fieldname": "against_blanket_order",
+ "fieldtype": "Check",
+ "label": "Against Blanket Order"
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-10-10 08:46:26.244823",
+ "modified": "2019-11-19 14:19:29.491945",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index dc2c4ce..c04bfd2 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -1,611 +1,158 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-06-25 10:25:16",
- "custom": 0,
- "description": "Settings for Selling Module",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Other",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "creation": "2013-06-25 10:25:16",
+ "description": "Settings for Selling Module",
+ "doctype": "DocType",
+ "document_type": "Other",
+ "engine": "InnoDB",
+ "field_order": [
+ "cust_master_name",
+ "campaign_naming_by",
+ "customer_group",
+ "territory",
+ "selling_price_list",
+ "close_opportunity_after_days",
+ "default_valid_till",
+ "column_break_5",
+ "so_required",
+ "dn_required",
+ "sales_update_frequency",
+ "maintain_same_sales_rate",
+ "editable_price_list_rate",
+ "allow_multiple_items",
+ "allow_against_multiple_purchase_orders",
+ "validate_selling_price",
+ "hide_tax_id"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Customer Name",
- "fieldname": "cust_master_name",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Customer Naming By",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Name\nNaming Series",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Customer Name",
+ "fieldname": "cust_master_name",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Customer Naming By",
+ "options": "Customer Name\nNaming Series"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "campaign_naming_by",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Campaign Naming By",
- "length": 0,
- "no_copy": 0,
- "options": "Campaign Name\nNaming Series",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "campaign_naming_by",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Campaign Naming By",
+ "options": "Campaign Name\nNaming Series"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "customer_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default Customer Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Customer Group",
+ "options": "Customer Group"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "territory",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default Territory",
- "length": 0,
- "no_copy": 0,
- "options": "Territory",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Territory",
+ "options": "Territory"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "selling_price_list",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default Price List",
- "length": 0,
- "no_copy": 0,
- "options": "Price List",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "selling_price_list",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Price List",
+ "options": "Price List"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "15",
- "description": "Auto close Opportunity after 15 days",
- "fieldname": "close_opportunity_after_days",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Close Opportunity After Days",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "15",
+ "description": "Auto close Opportunity after 15 days",
+ "fieldname": "close_opportunity_after_days",
+ "fieldtype": "Int",
+ "label": "Close Opportunity After Days"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_valid_till",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Quotation Validity Days",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "default_valid_till",
+ "fieldtype": "Data",
+ "label": "Default Quotation Validity Days"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "so_required",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Order Required",
- "length": 0,
- "no_copy": 0,
- "options": "No\nYes",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "so_required",
+ "fieldtype": "Select",
+ "label": "Sales Order Required",
+ "options": "No\nYes"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "dn_required",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Delivery Note Required",
- "length": 0,
- "no_copy": 0,
- "options": "No\nYes",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "dn_required",
+ "fieldtype": "Select",
+ "label": "Delivery Note Required",
+ "options": "No\nYes"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Each Transaction",
- "description": "How often should project and company be updated based on Sales Transactions.",
- "fieldname": "sales_update_frequency",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Update Frequency",
- "length": 0,
- "no_copy": 0,
- "options": "Each Transaction\nDaily\nMonthly",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Each Transaction",
+ "description": "How often should project and company be updated based on Sales Transactions.",
+ "fieldname": "sales_update_frequency",
+ "fieldtype": "Select",
+ "label": "Sales Update Frequency",
+ "options": "Each Transaction\nDaily\nMonthly",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintain_same_sales_rate",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintain Same Rate Throughout Sales Cycle",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "maintain_same_sales_rate",
+ "fieldtype": "Check",
+ "label": "Maintain Same Rate Throughout Sales Cycle"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "editable_price_list_rate",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow user to edit Price List Rate in transactions",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "editable_price_list_rate",
+ "fieldtype": "Check",
+ "label": "Allow user to edit Price List Rate in transactions"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "allow_multiple_items",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow Item to be added multiple times in a transaction",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "allow_multiple_items",
+ "fieldtype": "Check",
+ "label": "Allow Item to be added multiple times in a transaction"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "allow_against_multiple_purchase_orders",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow multiple Sales Orders against a Customer's Purchase Order",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "allow_against_multiple_purchase_orders",
+ "fieldtype": "Check",
+ "label": "Allow multiple Sales Orders against a Customer's Purchase Order"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "validate_selling_price",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "validate_selling_price",
+ "fieldtype": "Check",
+ "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hide_tax_id",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Hide Customer's Tax Id from Sales Transactions",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "hide_tax_id",
+ "fieldtype": "Check",
+ "label": "Hide Customer's Tax Id from Sales Transactions"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-cog",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-06-25 12:56:16.332039",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Selling Settings",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-cog",
+ "idx": 1,
+ "issingle": 1,
+ "modified": "2019-12-09 13:38:36.486298",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Selling Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/selling_settings/test_selling_settings.py b/erpnext/selling/doctype/selling_settings/test_selling_settings.py
new file mode 100644
index 0000000..961a54d
--- /dev/null
+++ b/erpnext/selling/doctype/selling_settings/test_selling_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestSellingSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json b/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json
similarity index 77%
rename from erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json
rename to erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json
index a0bb6fe..f39fea4 100644
--- a/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json
+++ b/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json
@@ -3,18 +3,18 @@
"app": "ERPNext",
"creation": "2019-11-15 14:44:10.065014",
"docstatus": 0,
- "doctype": "Setup Wizard Slide",
+ "doctype": "Onboarding Slide",
"domains": [],
"help_links": [
{
- "label": "Customers",
+ "label": "Learn More",
"video_id": "zsrrVDk6VBs"
}
],
"idx": 0,
- "image_src": "/assets/erpnext/images/illustrations/customer.png",
+ "image_src": "/assets/erpnext/images/illustrations/customers-onboard.png",
"max_count": 3,
- "modified": "2019-11-26 18:26:15.888794",
+ "modified": "2019-12-03 22:54:28.959549",
"modified_by": "Administrator",
"name": "Add A Few Customers",
"owner": "Administrator",
@@ -44,6 +44,5 @@
],
"slide_order": 40,
"slide_title": "Add A Few Customers",
- "slide_type": "Create",
- "submit_method": ""
+ "slide_type": "Create"
}
\ No newline at end of file
diff --git a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py
index 32711b2..056492a 100644
--- a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py
+++ b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py
@@ -7,7 +7,7 @@
def execute(filters=None):
if not filters: filters = {}
-
+
columns = get_columns()
iwq_map = get_item_warehouse_quantity_map()
item_map = get_item_details()
@@ -15,22 +15,23 @@
for sbom, warehouse in iwq_map.items():
total = 0
total_qty = 0
-
+
for wh, item_qty in warehouse.items():
total += 1
- row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description,
- item_map.get(sbom).stock_uom, wh]
- available_qty = item_qty
- total_qty += flt(available_qty)
- row += [available_qty]
-
- if available_qty:
- data.append(row)
- if (total == len(warehouse)):
- row = ["", "", "Total", "", "", total_qty]
+ if item_map.get(sbom):
+ row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description,
+ item_map.get(sbom).stock_uom, wh]
+ available_qty = item_qty
+ total_qty += flt(available_qty)
+ row += [available_qty]
+
+ if available_qty:
data.append(row)
+ if (total == len(warehouse)):
+ row = ["", "", "Total", "", "", total_qty]
+ data.append(row)
return columns, data
-
+
def get_columns():
columns = ["Item Code:Link/Item:100", "Item Name::100", "Description::120", \
"UOM:Link/UOM:80", "Warehouse:Link/Warehouse:100", "Quantity::100"]
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js
index ee806a7..daca2e3 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js
@@ -20,11 +20,15 @@
},
{
fieldname:"from_date",
+ reqd: 1,
label: __("From Date"),
fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
},
{
fieldname:"to_date",
+ reqd: 1,
+ default: frappe.datetime.get_today(),
label: __("To Date"),
fieldtype: "Date",
},
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 226c34f..1fc3663 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -196,6 +196,7 @@
def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters)
+
return frappe.db.sql("""
SELECT
so_item.item_code, so_item.item_name, so_item.item_group,
@@ -208,7 +209,6 @@
`tabSales Order` so, `tabSales Order Item` so_item
WHERE
so.name = so_item.parent
- AND so.company in (%s)
- AND so.docstatus = 1
- {0}
- """.format(conditions), company_list, as_dict=1) #nosec
+ AND so.company in ({0})
+ AND so.docstatus = 1 {1}
+ """.format(','.join(["%s"] * len(company_list)), conditions), tuple(company_list), as_dict=1)
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 04e8a83..ff35154 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -14,6 +14,9 @@
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils.nestedset import NestedSet
+from past.builtins import cmp
+import functools
+
class Company(NestedSet):
nsm_parent_field = 'parent_company'
@@ -560,3 +563,26 @@
return json.loads(history) if history and '{' in history else {}
return date_to_value_dict
+
+@frappe.whitelist()
+def get_default_company_address(name, sort_key='is_primary_address', existing_address=None):
+ if sort_key not in ['is_shipping_address', 'is_primary_address']:
+ return None
+
+ out = frappe.db.sql(""" SELECT
+ addr.name, addr.%s
+ FROM
+ `tabAddress` addr, `tabDynamic Link` dl
+ WHERE
+ dl.parent = addr.name and dl.link_doctype = 'Company' and
+ dl.link_name = %s and ifnull(addr.disabled, 0) = 0
+ """ %(sort_key, '%s'), (name)) #nosec
+
+ if existing_address:
+ if existing_address in [d[0] for d in out]:
+ return existing_address
+
+ if out:
+ return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
+ else:
+ return None
\ No newline at end of file
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 0bcddc2..4d2d540 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -283,7 +283,7 @@
card.value = card.value *-1
card.value = self.fmt_money(card.value,False if key in ("bank_balance", "credit_balance") else True)
- cache.setex(cache_key, card, 24 * 60 * 60)
+ cache.set_value(cache_key, card, expires_in_sec=24 * 60 * 60)
context.cards.append(card)
diff --git "a/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json" "b/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json"
new file mode 100644
index 0000000..bf330d0
--- /dev/null
+++ "b/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json"
@@ -0,0 +1,23 @@
+{
+ "add_more_button": 0,
+ "app": "ERPNext",
+ "creation": "2019-12-04 19:21:39.995776",
+ "docstatus": 0,
+ "doctype": "Onboarding Slide",
+ "domains": [],
+ "help_links": [],
+ "idx": 0,
+ "image_src": "/assets/erpnext/images/illustrations/desk-onboard.png",
+ "is_completed": 0,
+ "max_count": 3,
+ "modified": "2019-12-04 19:21:39.995776",
+ "modified_by": "Administrator",
+ "name": "Welcome back to ERPNext!",
+ "owner": "Administrator",
+ "slide_desc": "<p>Let's continue where you left from!</p>",
+ "slide_fields": [],
+ "slide_module": "Setup",
+ "slide_order": 0,
+ "slide_title": "Welcome back to ERPNext!",
+ "slide_type": "Continue"
+}
\ No newline at end of file
diff --git "a/erpnext/setup/setup_wizard_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json" "b/erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
similarity index 60%
rename from "erpnext/setup/setup_wizard_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
rename to "erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
index 1da9dd4..4ea6985 100644
--- "a/erpnext/setup/setup_wizard_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
+++ "b/erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
@@ -3,20 +3,20 @@
"app": "ERPNext",
"creation": "2019-11-26 17:01:26.671859",
"docstatus": 0,
- "doctype": "Setup Wizard Slide",
+ "doctype": "Onboarding Slide",
"domains": [],
"help_links": [],
"idx": 0,
- "image_src": "/assets/erpnext/images/illustrations/onboard.png",
+ "image_src": "/assets/erpnext/images/illustrations/desk-onboard.png",
"max_count": 0,
- "modified": "2019-11-26 17:17:29.813299",
+ "modified": "2019-12-03 22:49:12.871260",
"modified_by": "Administrator",
"name": "Welcome to ERPNext!",
"owner": "Administrator",
- "slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!<br>\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!",
+ "slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!",
"slide_fields": [],
"slide_module": "Setup",
- "slide_order": 10,
+ "slide_order": 1,
"slide_title": "Welcome to ERPNext!",
"slide_type": "Information"
}
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py
deleted file mode 100644
index a489133..0000000
--- a/erpnext/setup/setup_wizard/test_setup_wizard.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-
-import frappe, time
-from frappe.utils.selenium_testdriver import TestDriver
-
-def run_setup_wizard_test():
- driver = TestDriver()
- frappe.db.set_default('in_selenium', '1')
- frappe.db.commit()
-
- driver.login('#page-setup-wizard')
- print('Running Setup Wizard Test...')
-
- # Language slide
- driver.wait_for_ajax(True)
- time.sleep(1)
-
- driver.set_select("language", "English (United States)")
- driver.wait_for_ajax(True)
- time.sleep(1)
- driver.click(".next-btn")
-
- # Region slide
- driver.wait_for_ajax(True)
- driver.set_select("country", "India")
- driver.wait_for_ajax(True)
- time.sleep(1)
- driver.click(".next-btn")
-
- # Profile slide
- driver.set_field("full_name", "Great Tester")
- driver.set_field("email", "great@example.com")
- driver.set_field("password", "test")
- driver.wait_for_ajax(True)
- time.sleep(1)
- driver.click(".next-btn")
- time.sleep(1)
-
- # domain slide
- driver.set_multicheck("domains", ["Manufacturing"])
- time.sleep(1)
- driver.click(".next-btn")
-
- # Org slide
- driver.set_field("company_name", "For Testing")
- time.sleep(1)
- driver.print_console()
- driver.click(".next-btn")
-
- driver.set_field("company_tagline", "Just for GST")
- driver.set_field("bank_account", "HDFC")
- time.sleep(3)
- driver.click(".complete-btn")
-
- # Wait for desktop
- driver.wait_for('#page-desktop', timeout=600)
-
- driver.print_console()
- time.sleep(3)
-
- frappe.db.set_default('in_selenium', None)
- frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT")
- frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT")
- frappe.db.commit()
-
- driver.close()
-
- return True
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index d1c206d..1a86b79 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -106,7 +106,8 @@
# expire in 6 hours
response.raise_for_status()
value = response.json()["rates"][to_currency]
- cache.setex(key, value, 6 * 60 * 60)
+
+ cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
return flt(value)
except:
frappe.log_error(title="Get Exchange Rate")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index c98dfe3..39aad2e 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -424,7 +424,12 @@
target.run_method("calculate_taxes_and_totals")
# set company address
- target.update(get_company_address(target.company))
+ if source.company_address:
+ target.update({'company_address': source.company_address})
+ else:
+ # set company address
+ target.update(get_company_address(target.company))
+
if target.company_address:
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
diff --git a/erpnext/stock/doctype/delivery_note/regional/india.js b/erpnext/stock/doctype/delivery_note/regional/india.js
new file mode 100644
index 0000000..22f4716
--- /dev/null
+++ b/erpnext/stock/doctype/delivery_note/regional/india.js
@@ -0,0 +1,4 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Delivery Note');
+
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 410d9f1..e3d356f 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -136,6 +136,20 @@
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
},
+ gst_hsn_code: function(frm){
+ if(!frm.doc.taxes){
+ frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc=>{
+ frm.doc.taxes = [];
+ $.each(hsn_doc.taxes || [], function(i, tax) {
+ let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
+ a.item_tax_template = tax.item_tax_template;
+ a.tax_category = tax.tax_category;
+ frm.refresh_field('taxes');
+ });
+ });
+ }
+ },
+
is_fixed_asset: function(frm) {
// set serial no to false & toggles its visibility
frm.set_value('has_serial_no', 0);
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index 831381c..5341f29 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -65,7 +65,7 @@
bin = get_bin_qty(packing_item_code, pi.warehouse)
pi.actual_qty = flt(bin.get("actual_qty"))
pi.projected_qty = flt(bin.get("projected_qty"))
- if old_packed_items_map:
+ if old_packed_items_map and old_packed_items_map.get((packing_item_code, main_item_row.item_code)):
pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no
pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no
pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index d5914f9..6b5e40e 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -34,6 +34,12 @@
filters: {'company': frm.doc.company }
}
});
+
+ frm.set_query("taxes_and_charges", function() {
+ return {
+ filters: {'company': frm.doc.company }
+ }
+ });
},
onload: function(frm) {
@@ -296,4 +302,4 @@
}
});
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d0fae6a..060175f 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -95,7 +95,8 @@
# check cwip accounts before making auto assets
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
arbnb_account = self.get_company_default("asset_received_but_not_billed")
- cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
+ cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \
+ company = self.company)
break
def validate_with_previous_doc(self):
@@ -244,7 +245,7 @@
negative_expense_to_be_booked += flt(d.item_tax_amount)
# Amount added through landed-cost-voucher
- if landed_cost_entries:
+ if d.landed_cost_voucher_amount and landed_cost_entries:
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
gl_entries.append(self.get_gl_dict({
"account": account,
@@ -364,8 +365,9 @@
def add_asset_gl_entries(self, item, gl_entries):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
- # This returns company's default cwip account
- cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
+ # This returns category's cwip account if not then fallback to company's default cwip account
+ cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \
+ company = self.company)
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
@@ -609,7 +611,7 @@
def get_item_account_wise_additional_cost(purchase_document):
landed_cost_voucher = frappe.get_value("Landed Cost Purchase Receipt",
- {"receipt_document": purchase_document}, "parent")
+ {"receipt_document": purchase_document, "docstatus": 1}, "parent")
if not landed_cost_voucher:
return
@@ -620,8 +622,7 @@
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
for item in landed_cost_voucher_doc.items:
- if item.receipt_document == purchase_document:
- total_item_cost += item.get(based_on_field)
+ total_item_cost += item.get(based_on_field)
for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document:
diff --git a/erpnext/stock/doctype/purchase_receipt/regional/india.js b/erpnext/stock/doctype/purchase_receipt/regional/india.js
new file mode 100644
index 0000000..b4f1201
--- /dev/null
+++ b/erpnext/stock/doctype/purchase_receipt/regional/india.js
@@ -0,0 +1,3 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Purchase Receipt');
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 19eb398..23d00da 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -353,17 +353,19 @@
def auto_make_serial_nos(args):
serial_nos = get_serial_nos(args.get('serial_no'))
created_numbers = []
+ voucher_type = args.get('voucher_type')
+ item_code = args.get('item_code')
for serial_no in serial_nos:
if frappe.db.exists("Serial No", serial_no):
sr = frappe.get_doc("Serial No", serial_no)
sr.via_stock_ledger = True
- sr.item_code = args.get('item_code')
+ sr.item_code = item_code
sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None
sr.batch_no = args.get('batch_no')
sr.location = args.get('location')
sr.company = args.get('company')
sr.supplier = args.get('supplier')
- if sr.sales_order and args.get('voucher_type') == "Stock Entry" \
+ if sr.sales_order and voucher_type == "Stock Entry" \
and not args.get('actual_qty', 0) > 0:
sr.sales_order = None
sr.save(ignore_permissions=True)
@@ -371,10 +373,28 @@
created_numbers.append(make_serial_no(serial_no, args))
form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
+
+ # Setting up tranlated title field for all cases
+ singular_title = _("Serial Number Created")
+ multiple_title = _("Serial Numbers Created")
+
+ if voucher_type:
+ multiple_title = singular_title = _("{0} Created").format(voucher_type)
+
if len(form_links) == 1:
- frappe.msgprint(_("Serial No {0} created").format(form_links[0]))
+ frappe.msgprint(_("Serial No {0} Created").format(form_links[0]), singular_title)
elif len(form_links) > 0:
- frappe.msgprint(_("The following serial numbers were created: <br> {0}").format(', '.join(form_links)))
+ message = _("The following serial numbers were created: <br><br> {0}").format(get_items_html(form_links, item_code))
+ frappe.msgprint(message, multiple_title)
+
+def get_items_html(serial_nos, item_code):
+ body = ', '.join(serial_nos)
+ return '''<details><summary>
+ <b>{0}:</b> {1} Serial Numbers <span class="caret"></span>
+ </summary>
+ <div class="small">{2}</div></details>
+ '''.format(item_code, len(serial_nos), body)
+
def get_item_details(item_code):
return frappe.db.sql("""select name, has_batch_no, docstatus,
@@ -397,7 +417,7 @@
sr.via_stock_ledger = args.get('via_stock_ledger') or True
sr.asset = args.get('asset')
sr.location = args.get('location')
-
+
if args.get('purchase_document_type'):
sr.purchase_document_type = args.get('purchase_document_type')
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index d9c94fc..79ce231 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -249,6 +249,8 @@
}, __("Get items from"));
}
+ frm.events.show_bom_custom_button(frm);
+
if (frm.doc.company) {
frm.trigger("toggle_display_account_head");
}
@@ -262,6 +264,11 @@
frm.trigger("setup_quality_inspection");
},
+ stock_entry_type: function(frm){
+ frm.remove_custom_button('Bill of Materials', "Get items from");
+ frm.events.show_bom_custom_button(frm);
+ },
+
purpose: function(frm) {
frm.trigger('validate_purpose_consumption');
frm.fields_dict.items.grid.refresh();
@@ -398,6 +405,85 @@
}
},
+ show_bom_custom_button: function(frm){
+ if (frm.doc.docstatus === 0 &&
+ ['Material Issue', 'Material Receipt', 'Material Transfer', 'Send to Subcontractor'].includes(frm.doc.purpose)) {
+ frm.add_custom_button(__('Bill of Materials'), function() {
+ frm.events.get_items_from_bom(frm);
+ }, __("Get items from"));
+ }
+ },
+
+ get_items_from_bom: function(frm) {
+ let filters = function(){
+ return {filters: { docstatus:1 }};
+ }
+
+ let fields = [
+ {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
+ options:"BOM", reqd: 1, get_query: filters()},
+ {"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
+ options:"Warehouse"},
+ {"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
+ options:"Warehouse"},
+ {"fieldname":"qty", "fieldtype":"Float", "label":__("Quantity"),
+ reqd: 1, "default": 1},
+ {"fieldname":"fetch_exploded", "fieldtype":"Check",
+ "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1},
+ {"fieldname":"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"}
+ ]
+
+ // Exclude field 'Target Warehouse' in case of Material Issue
+ if (frm.doc.purpose == 'Material Issue'){
+ fields.splice(2,1);
+ }
+ // Exclude field 'Source Warehouse' in case of Material Receipt
+ else if(frm.doc.purpose == 'Material Receipt'){
+ fields.splice(1,1);
+ }
+
+ let d = new frappe.ui.Dialog({
+ title: __("Get Items from BOM"),
+ fields: fields
+ });
+ d.get_input("fetch").on("click", function() {
+ let values = d.get_values();
+ if(!values) return;
+ values["company"] = frm.doc.company;
+ if(!frm.doc.company) frappe.throw(__("Company field is required"));
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
+ args: values,
+ callback: function(r) {
+ if (!r.message) {
+ frappe.throw(__("BOM does not contain any stock item"));
+ } else {
+ erpnext.utils.remove_empty_first_row(frm, "items");
+ $.each(r.message, function(i, item) {
+ let d = frappe.model.add_child(cur_frm.doc, "Stock Entry Detail", "items");
+ d.item_code = item.item_code;
+ d.item_name = item.item_name;
+ d.item_group = item.item_group;
+ d.s_warehouse = values.source_warehouse;
+ d.t_warehouse = values.target_warehouse;
+ d.uom = item.stock_uom;
+ d.stock_uom = item.stock_uom;
+ d.conversion_factor = item.conversion_factor ? item.conversion_factor : 1;
+ d.qty = item.qty;
+ d.expense_account = item.expense_account;
+ d.project = item.project;
+ frm.events.set_basic_rate(frm, d.doctype, d.name);
+ });
+ }
+ d.hide();
+ refresh_field("items");
+ }
+ });
+
+ });
+ d.show();
+ },
+
calculate_basic_amount: function(frm, item) {
item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate),
precision("basic_amount", item));
@@ -536,7 +622,7 @@
if(r.message) {
var d = locals[cdt][cdn];
$.each(r.message, function(k, v) {
- d[k] = v;
+ frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
});
refresh_field("items");
erpnext.stock.select_batch_and_serial_no(frm, d);
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f81fa68..00d27ef 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -649,6 +649,12 @@
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
+ divide_based_on = total_basic_amount
+
+ if self.get("additional_costs") and not total_basic_amount:
+ # if total_basic_amount is 0, distribute additional charges based on qty
+ divide_based_on = sum(item.qty for item in list(self.get("items")))
+
item_account_wise_additional_cost = {}
for t in self.get("additional_costs"):
@@ -656,8 +662,11 @@
if d.t_warehouse:
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0)
+
+ multiply_based_on = d.basic_amount if total_basic_amount else d.qty
+
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \
- (t.amount * d.basic_amount) / total_basic_amount
+ (t.amount * multiply_based_on) / divide_based_on
if item_account_wise_additional_cost:
for d in self.get("items"):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index eddab5d..ee5f237 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -790,6 +790,50 @@
filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening")
self.assertEqual(is_opening, "Yes")
+ def test_total_basic_amount_zero(self):
+ se = frappe.get_doc({"doctype":"Stock Entry",
+ "purpose":"Material Receipt",
+ "stock_entry_type":"Material Receipt",
+ "posting_date": nowdate(),
+ "company":"_Test Company with perpetual inventory",
+ "items":[
+ {
+ "item_code":"Basil Leaves",
+ "description":"Basil Leaves",
+ "qty": 1,
+ "basic_rate": 0,
+ "uom":"Nos",
+ "t_warehouse": "Stores - TCP1",
+ "allow_zero_valuation_rate": 1,
+ "cost_center": "Main - TCP1"
+ },
+ {
+ "item_code":"Basil Leaves",
+ "description":"Basil Leaves",
+ "qty": 2,
+ "basic_rate": 0,
+ "uom":"Nos",
+ "t_warehouse": "Stores - TCP1",
+ "allow_zero_valuation_rate": 1,
+ "cost_center": "Main - TCP1"
+ },
+ ],
+ "additional_costs":[
+ {"expense_account":"Miscellaneous Expenses - TCP1",
+ "amount":100,
+ "description": "miscellanous"}
+ ]
+ })
+ se.insert()
+ se.submit()
+
+ self.check_gl_entries("Stock Entry", se.name,
+ sorted([
+ ["Stock Adjustment - TCP1", 100.0, 0.0],
+ ["Miscellaneous Expenses - TCP1", 0.0, 100.0]
+ ])
+ )
+
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 55f4be1..76644ed 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -213,7 +213,8 @@
project: "",
qty: "",
stock_qty: "",
- conversion_factor: ""
+ conversion_factor: "",
+ against_blanket_order: 0/1
}
:param item: `item_code` of Item object
:return: frappe._dict
@@ -302,7 +303,8 @@
"weight_per_unit":item.weight_per_unit,
"weight_uom":item.weight_uom,
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
- "transaction_date": args.get("transaction_date")
+ "transaction_date": args.get("transaction_date"),
+ "against_blanket_order": args.get("against_blanket_order")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -996,9 +998,10 @@
def update_party_blanket_order(args, out):
- blanket_order_details = get_blanket_order_details(args)
- if blanket_order_details:
- out.update(blanket_order_details)
+ if out["against_blanket_order"]:
+ blanket_order_details = get_blanket_order_details(args)
+ if blanket_order_details:
+ out.update(blanket_order_details)
@frappe.whitelist()
def get_blanket_order_details(args):
diff --git a/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json b/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
similarity index 76%
rename from erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
rename to erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
index c536f7b..27a3062 100644
--- a/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
+++ b/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
@@ -3,13 +3,13 @@
"app": "ERPNext",
"creation": "2019-11-15 14:41:12.007359",
"docstatus": 0,
- "doctype": "Setup Wizard Slide",
+ "doctype": "Onboarding Slide",
"domains": [],
"help_links": [],
"idx": 0,
- "image_src": "/assets/erpnext/images/illustrations/product.png",
+ "image_src": "/assets/erpnext/images/illustrations/products-onboard.png",
"max_count": 3,
- "modified": "2019-11-26 18:26:35.305755",
+ "modified": "2019-12-03 22:54:07.558632",
"modified_by": "Administrator",
"name": "Add A Few Products You Buy Or Sell",
"owner": "Administrator",
@@ -26,15 +26,9 @@
},
{
"align": "",
- "fieldtype": "Column Break",
- "reqd": 1
- },
- {
- "align": "",
- "fieldname": "uom",
- "fieldtype": "Link",
- "label": "UOM",
- "options": "UOM",
+ "fieldname": "item_price",
+ "fieldtype": "Currency",
+ "label": "Item Price",
"reqd": 1
},
{
@@ -44,14 +38,14 @@
},
{
"align": "",
- "fieldname": "item_price",
- "fieldtype": "Currency",
- "label": "Item Price",
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "UOM",
"reqd": 1
}
],
"slide_order": 30,
"slide_title": "Add A Few Products You Buy Or Sell",
- "slide_type": "Create",
- "submit_method": ""
+ "slide_type": "Create"
}
\ No newline at end of file
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
index caf7eb8..48c0f42 100644
--- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
+++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
@@ -15,7 +15,7 @@
"prepared_report": 0,
"query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`",
"ref_doctype": "Purchase Order",
- "report_name": "Purchase Order Items To Be Received or Billed1",
+ "report_name": "Purchase Order Items To Be Received or Billed",
"report_type": "Query Report",
"roles": [
{
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index db7f6ad..d757ecb 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -122,8 +122,8 @@
cf_field = cf_join = ""
if include_uom:
cf_field = ", ucd.conversion_factor"
- cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \
- % (include_uom)
+ cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \
+ % frappe.db.escape(include_uom)
res = frappe.db.sql("""
select
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 652114f..9b904ad 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2019-06-27 22:58:49.609672",
+ "modified": "2019-12-10 13:48:19.894186",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 2e25a12..cdff3ff 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1329,7 +1329,7 @@
apps/erpnext/erpnext/utilities/transaction_base.py,Invalid Posting Time,Ungültige Buchungszeit
DocType: Salary Component,Condition and Formula,Zustand und Formel
DocType: Lead,Campaign Name,Kampagnenname
-apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss der Aufgabe
+apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss des Vorgangs
apps/erpnext/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py,There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1}
DocType: Fee Validity,Healthcare Practitioner,praktischer Arzt
DocType: Hotel Room,Capacity,Kapazität
@@ -1353,7 +1353,7 @@
apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Zahlung abgebrochen. Bitte überprüfen Sie Ihr GoCardless Konto für weitere Details
DocType: Work Order,Skip Material Transfer to WIP Warehouse,Überspringen Sie die Materialübertragung in das WIP-Lager
DocType: Contract,N/A,nicht verfügbar
-DocType: Task Type,Task Type,Aufgabentyp
+DocType: Task Type,Task Type,Vorgangstyp
DocType: Topic,Topic Content,Themeninhalt
DocType: Delivery Settings,Send with Attachment,Senden mit Anhang
DocType: Service Level,Priorities,Prioritäten
@@ -2449,7 +2449,7 @@
DocType: Asset,Depreciation Schedules,Abschreibungen Termine
apps/erpnext/erpnext/projects/doctype/timesheet/timesheet.js,Create Sales Invoice,Verkaufsrechnung erstellen
apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Ineligible ITC,Nicht förderfähiges ITC
-DocType: Task,Dependent Tasks,Abhängige Aufgaben
+DocType: Task,Dependent Tasks,Abhängige Vorgänge
apps/erpnext/erpnext/regional/report/gstr_1/gstr_1.py,Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden:
apps/erpnext/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js,Quantity to Produce,Menge zu produzieren
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen
@@ -2846,7 +2846,7 @@
DocType: Purchase Invoice,03-Deficiency in services,03-Mangel an Dienstleistungen
DocType: Healthcare Settings,Default Medical Code Standard,Default Medical Code Standard
DocType: Purchase Invoice Item,HSN/SAC,HSN / SAC
-DocType: Project Template Task,Project Template Task,Projektvorlagenaufgabe
+DocType: Project Template Task,Project Template Task,Projektvorgangsvorlage
DocType: Accounts Settings,Over Billing Allowance (%),Mehr als Abrechnungsbetrag (%)
apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen
DocType: Company,Default Payable Account,Standard-Verbindlichkeitenkonto
@@ -3323,7 +3323,7 @@
,Qty to Order,Zu bestellende Menge
DocType: Period Closing Voucher,"The account head under Liability or Equity, in which Profit/Loss will be booked","Der Kontenkopf unter Eigen- oder Fremdkapital, in dem Gewinn / Verlust verbucht wird"
apps/erpnext/erpnext/accounts/doctype/budget/budget.py,Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},Ein weiterer Budgeteintrag '{0}' existiert bereits für {1} '{2}' und für '{3}' für das Geschäftsjahr {4}
-apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Aufgaben
+apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Vorgänge
DocType: Opportunity,Mins to First Response,Minuten zum First Response
DocType: Pricing Rule,Margin Type,Margenart
apps/erpnext/erpnext/projects/doctype/project/project_dashboard.html,{0} hours,{0} Stunden
@@ -3961,7 +3961,7 @@
apps/erpnext/erpnext/education/doctype/student/student_dashboard.py,Student LMS Activity,Student LMS Aktivität
DocType: POS Profile,Applicable for Users,Anwendbar für Benutzer
DocType: Supplier Quotation,PUR-SQTN-.YYYY.-,PUR-SQTN-.JJJJ.-
-apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Aufgaben auf Status {0} setzen?
+apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Vorgänge auf Status {0} setzen?
DocType: Purchase Invoice,Set Advances and Allocate (FIFO),Vorschüsse setzen und zuordnen (FIFO)
apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,No Work Orders created,Keine Arbeitsaufträge erstellt
apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py,Salary Slip of employee {0} already created for this period,Gehaltsabrechnung der Mitarbeiter {0} für diesen Zeitraum bereits erstellt
@@ -4418,7 +4418,7 @@
DocType: Hotel Room,Hotels,Hotels
apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,New Cost Center Name,Neuer Kostenstellenname
DocType: Leave Control Panel,Leave Control Panel,Urlaubsverwaltung
-DocType: Project,Task Completion,Aufgabenerledigung
+DocType: Project,Task Completion,Vorgangserfüllung
apps/erpnext/erpnext/templates/generators/item/item_add_to_cart.html,Not in Stock,Nicht lagernd
DocType: Volunteer,Volunteer Skills,Freiwillige Fähigkeiten
DocType: Additional Salary,HR User,Nutzer Personalabteilung
@@ -5197,7 +5197,7 @@
apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py,Account {0} does not exists,Konto {0} existiert nicht
apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.js,Select Loyalty Program,Wählen Sie Treueprogramm
DocType: Project,Project Type,Projekttyp
-apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diese Aufgabe existiert eine untergeordnete Aufgabe. Sie können diese Aufgabe daher nicht löschen.
+apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diesen Vorgang existiert ein untergeordneter Vorgang. Sie können diese Aufgabe daher nicht löschen.
apps/erpnext/erpnext/setup/doctype/sales_person/sales_person.py,Either target qty or target amount is mandatory.,Entweder Zielstückzahl oder Zielmenge ist zwingend erforderlich.
apps/erpnext/erpnext/config/projects.py,Cost of various activities,Aufwendungen für verschiedene Tätigkeiten
apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py,"Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Einstellen Events auf {0}, da die Mitarbeiter auf die beigefügten unter Verkaufs Personen keine Benutzer-ID {1}"
@@ -5597,7 +5597,7 @@
apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,{0} is not a valid Batch Number for Item {1},{0} ist keine gültige Chargennummer für Artikel {1}
apps/erpnext/erpnext/shopping_cart/cart.py,Please enter valid coupon code !!,Bitte geben Sie einen gültigen Gutscheincode ein !!
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Note: There is not enough leave balance for Leave Type {0},Hinweis: Es gibt nicht genügend Urlaubsguthaben für Abwesenheitstyp {0}
-DocType: Task,Task Description,Aufgabenbeschreibung
+DocType: Task,Task Description,Vorgangsbeschreibung
DocType: Training Event,Seminar,Seminar
DocType: Program Enrollment Fee,Program Enrollment Fee,Programm Einschreibegebühr
DocType: Item,Supplier Items,Lieferantenartikel
@@ -5754,7 +5754,7 @@
DocType: Lost Reason Detail,Lost Reason Detail,Verlorene Begründung Detail
apps/erpnext/erpnext/hr/utils.py,Please set leave policy for employee {0} in Employee / Grade record,Legen Sie die Abwesenheitsrichtlinie für den Mitarbeiter {0} im Mitarbeiter- / Notensatz fest
apps/erpnext/erpnext/public/js/controllers/transaction.js,Invalid Blanket Order for the selected Customer and Item,Ungültiger Blankoauftrag für den ausgewählten Kunden und Artikel
-apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Aufgaben hinzufügen
+apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Vorgänge hinzufügen
DocType: Purchase Invoice,Items,Artikel
apps/erpnext/erpnext/crm/doctype/contract/contract.py,End Date cannot be before Start Date.,Das Enddatum darf nicht vor dem Startdatum liegen.
apps/erpnext/erpnext/education/doctype/course_enrollment/course_enrollment.py,Student is already enrolled.,Student ist bereits eingetragen sind.
diff --git a/erpnext/www/book_appointment/__init__.py b/erpnext/www/book_appointment/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/www/book_appointment/__init__.py
diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book_appointment/index.css
similarity index 100%
rename from erpnext/www/book-appointment/index.css
rename to erpnext/www/book_appointment/index.css
diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book_appointment/index.html
similarity index 98%
rename from erpnext/www/book-appointment/index.html
rename to erpnext/www/book_appointment/index.html
index 96774d5..f242f43 100644
--- a/erpnext/www/book-appointment/index.html
+++ b/erpnext/www/book_appointment/index.html
@@ -4,7 +4,7 @@
{% block script %}
<script src="assets/js/moment-bundle.min.js"></script>
-<script src="book-appointment/index.js"></script>
+<script src="book_appointment/index.js"></script>
{% endblock %}
{% block page_content %}
diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book_appointment/index.js
similarity index 96%
rename from erpnext/www/book-appointment/index.js
rename to erpnext/www/book_appointment/index.js
index 13c87dd..c8dd501 100644
--- a/erpnext/www/book-appointment/index.js
+++ b/erpnext/www/book_appointment/index.js
@@ -15,10 +15,10 @@
async function get_global_variables() {
// Using await through this file instead of then.
window.appointment_settings = (await frappe.call({
- method: 'erpnext.www.book-appointment.index.get_appointment_settings'
+ method: 'erpnext.www.book_appointment.index.get_appointment_settings'
})).message;
window.timezones = (await frappe.call({
- method:'erpnext.www.book-appointment.index.get_timezones'
+ method:'erpnext.www.book_appointment.index.get_timezones'
})).message;
window.holiday_list = window.appointment_settings.holiday_list;
}
@@ -79,7 +79,7 @@
async function get_time_slots(date, timezone) {
let slots = (await frappe.call({
- method: 'erpnext.www.book-appointment.index.get_appointment_slots',
+ method: 'erpnext.www.book_appointment.index.get_appointment_slots',
args: {
date: date,
timezone: timezone
@@ -201,7 +201,7 @@
}
let contact = get_form_data();
let appointment = frappe.call({
- method: 'erpnext.www.book-appointment.index.create_appointment',
+ method: 'erpnext.www.book_appointment.index.create_appointment',
args: {
'date': window.selected_date,
'time': window.selected_time,
diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book_appointment/index.py
similarity index 100%
rename from erpnext/www/book-appointment/index.py
rename to erpnext/www/book_appointment/index.py
diff --git a/erpnext/www/book_appointment/verify/__init__.py b/erpnext/www/book_appointment/verify/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/www/book_appointment/verify/__init__.py
diff --git a/erpnext/www/book-appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html
similarity index 100%
rename from erpnext/www/book-appointment/verify/index.html
rename to erpnext/www/book_appointment/verify/index.html
diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py
similarity index 100%
rename from erpnext/www/book-appointment/verify/index.py
rename to erpnext/www/book_appointment/verify/index.py