From ec5ec6076418ecc4ca1d8d10edc2b1228856d8c6 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 5 Mar 2015 19:31:23 +0530 Subject: [PATCH] Fixed Test Cases --- .../accounts/doctype/account/test_account.py | 1 + .../doctype/cost_center/test_records.json | 31 +- .../doctype/pricing_rule/test_pricing_rule.py | 1 + .../purchase_invoice/test_records.json | 8 +- .../doctype/sales_invoice/test_records.json | 4 +- .../sales_invoice/test_sales_invoice.py | 10 +- .../test_records.json | 212 +++--- erpnext/controllers/taxes_and_totals.py | 31 +- .../crm/doctype/opportunity/opportunity.py | 3 + erpnext/hr/doctype/employee/employee.py | 8 +- .../manufacturing_settings.json | 158 ++--- .../production_order/production_order.js | 2 +- .../production_order/production_order.json | 603 +++++++++--------- .../production_order/production_order.py | 20 +- .../production_order/test_production_order.py | 57 +- .../doctype/workstation/test_records.json | 5 +- .../doctype/workstation/test_workstation.py | 12 +- .../doctype/workstation/workstation.py | 53 +- .../workstation_working_hour.json | 230 +++---- .../projects/doctype/project/test_project.py | 3 +- erpnext/projects/doctype/task/test_task.py | 4 +- .../doctype/time_log/test_records.json | 16 +- .../doctype/time_log/test_time_log.py | 24 +- .../projects/doctype/time_log/time_log.json | 438 ++++++------- erpnext/projects/doctype/time_log/time_log.py | 36 +- .../time_log_batch/test_time_log_batch.py | 3 +- .../stock/doctype/stock_entry/stock_entry.py | 4 +- 27 files changed, 1005 insertions(+), 972 deletions(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index c37f0757dc2..5c9b5b6c2ba 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -29,6 +29,7 @@ def _make_test_records(verbose): ["_Test Account S&H Education Cess", "_Test Account Tax Assets", "Ledger", "Tax"], ["_Test Account CST", "Direct Expenses", "Ledger", "Tax"], ["_Test Account Discount", "Direct Expenses", "Ledger", None], + ["_Test Write Off", "Indirect Expenses", "Ledger", None], # related to Account Inventory Integration ["_Test Account Stock In Hand", "Current Assets", "Ledger", None], diff --git a/erpnext/accounts/doctype/cost_center/test_records.json b/erpnext/accounts/doctype/cost_center/test_records.json index 13e0ea9972d..7486214973f 100644 --- a/erpnext/accounts/doctype/cost_center/test_records.json +++ b/erpnext/accounts/doctype/cost_center/test_records.json @@ -2,25 +2,32 @@ { "budgets": [ { - "account": "_Test Account Cost for Goods Sold - _TC", - "budget_allocated": 100000, - "doctype": "Budget Detail", - "fiscal_year": "_Test Fiscal Year 2013", + "account": "_Test Account Cost for Goods Sold - _TC", + "budget_allocated": 100000, + "doctype": "Budget Detail", + "fiscal_year": "_Test Fiscal Year 2013", "parentfield": "budgets" } - ], - "company": "_Test Company", - "cost_center_name": "_Test Cost Center", - "distribution_id": "_Test Distribution", - "doctype": "Cost Center", - "group_or_ledger": "Ledger", + ], + "company": "_Test Company", + "cost_center_name": "_Test Cost Center", + "distribution_id": "_Test Distribution", + "doctype": "Cost Center", + "group_or_ledger": "Ledger", "parent_cost_center": "_Test Company - _TC" }, { "company": "_Test Company", - "cost_center_name": "_Test Cost Center 2", + "cost_center_name": "_Test Cost Center 2", + "doctype": "Cost Center", + "group_or_ledger": "Ledger", + "parent_cost_center": "_Test Company - _TC" + }, + { + "company": "_Test Company", + "cost_center_name": "_Test Write Off Cost Center", "doctype": "Cost Center", "group_or_ledger": "Ledger", "parent_cost_center": "_Test Company - _TC" } -] \ No newline at end of file +] diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 445bed284a7..2a43814fab8 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -15,6 +15,7 @@ class TestPricingRule(unittest.TestCase): test_record = { "doctype": "Pricing Rule", + "title": "_Test Pricing Rule", "apply_on": "Item Code", "item_code": "_Test Item", "selling": 1, diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 9139e0c542d..679e87024ac 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -53,7 +53,7 @@ "description": "Shipping Charges", "doctype": "Purchase Taxes and Charges", "parentfield": "taxes", - "rate": 100 + "tax_amount": 100 }, { "account_head": "_Test Account Customs Duty - _TC", @@ -176,7 +176,7 @@ "description": "Shipping Charges", "doctype": "Purchase Taxes and Charges", "parentfield": "taxes", - "rate": 100.0 + "tax_amount": 100.0 }, { "account_head": "_Test Account VAT - _TC", @@ -187,7 +187,7 @@ "description": "VAT", "doctype": "Purchase Taxes and Charges", "parentfield": "taxes", - "rate": 120.0 + "tax_amount": 120.0 }, { "account_head": "_Test Account Customs Duty - _TC", @@ -198,7 +198,7 @@ "description": "Customs Duty", "doctype": "Purchase Taxes and Charges", "parentfield": "taxes", - "rate": 150.0 + "tax_amount": 150.0 } ], "posting_date": "2013-02-03", diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index dc69339d9d5..7726835967f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -174,7 +174,7 @@ "description": "Shipping Charges", "doctype": "Sales Taxes and Charges", "parentfield": "taxes", - "rate": 100 + "tax_amount": 100 }, { "account_head": "_Test Account Customs Duty - _TC", @@ -367,7 +367,7 @@ "doctype": "Sales Taxes and Charges", "idx": 7, "parentfield": "taxes", - "rate": 100 + "tax_amount": 100 }, { "account_head": "_Test Account Discount - _TC", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b9ea4553021..1ceedad3020 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -254,9 +254,9 @@ class TestSalesInvoice(unittest.TestCase): expected_values = { "keys": ["price_list_rate", "discount_percentage", "rate", "amount", - "base_price_list_rate", "base_rate", "base_amount"], - "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 499.98], - "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 750], + "base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"], + "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.98], + "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 750], } # check if children are saved @@ -500,7 +500,9 @@ class TestSalesInvoice(unittest.TestCase): "naming_series": "_T-POS Setting-", "selling_price_list": "_Test Price List", "territory": "_Test Territory", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "_Test Warehouse - _TC", + "write_off_account": "_Test Write Off - _TC", + "write_off_cost_center": "_Test Write Off Cost Center - _TC" }) if not frappe.db.exists("POS Setting", "_Test POS Setting"): diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_master/test_records.json b/erpnext/accounts/doctype/sales_taxes_and_charges_master/test_records.json index dd9c595fe97..12d4fca1202 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_master/test_records.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_master/test_records.json @@ -1,157 +1,157 @@ [ { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Master", + "company": "_Test Company", + "doctype": "Sales Taxes and Charges Master", "taxes": [ { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 6 - }, + }, { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 6.36 } - ], - "title": "_Test Sales Taxes and Charges Master", + ], + "title": "_Test Sales Taxes and Charges Master", "territories": [ { - "doctype": "Applicable Territory", - "parentfield": "territories", + "doctype": "Applicable Territory", + "parentfield": "territories", "territory": "All Territories" - }, + }, { - "doctype": "Applicable Territory", - "parentfield": "territories", + "doctype": "Applicable Territory", + "parentfield": "territories", "territory": "_Test Territory Rest Of The World" } ] - }, + }, { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Master", + "company": "_Test Company", + "doctype": "Sales Taxes and Charges Master", "taxes": [ { - "account_head": "_Test Account Shipping Charges - _TC", - "charge_type": "Actual", - "cost_center": "_Test Cost Center - _TC", - "description": "Shipping Charges", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 100 - }, + "account_head": "_Test Account Shipping Charges - _TC", + "charge_type": "Actual", + "cost_center": "_Test Cost Center - _TC", + "description": "Shipping Charges", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "tax_amount": 100 + }, { - "account_head": "_Test Account Customs Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Customs Duty", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account Customs Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Customs Duty", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 10 - }, + }, { - "account_head": "_Test Account Excise Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Excise Duty", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 12 - }, + }, { - "account_head": "_Test Account Education Cess - _TC", - "charge_type": "On Previous Row Amount", - "cost_center": "_Test Cost Center - _TC", - "description": "Education Cess", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 2, + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 2, "row_id": 3 - }, + }, { - "account_head": "_Test Account S&H Education Cess - _TC", - "charge_type": "On Previous Row Amount", - "cost_center": "_Test Cost Center - _TC", - "description": "S&H Education Cess", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 1, + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 1, "row_id": 3 - }, + }, { - "account_head": "_Test Account CST - _TC", - "charge_type": "On Previous Row Total", - "cost_center": "_Test Cost Center - _TC", - "description": "CST", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 2, + "account_head": "_Test Account CST - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "CST", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 2, "row_id": 5 - }, + }, { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 12.5 - }, + }, { - "account_head": "_Test Account Discount - _TC", - "charge_type": "On Previous Row Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Discount", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": -10, + "account_head": "_Test Account Discount - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Discount", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": -10, "row_id": 7 } - ], - "title": "_Test India Tax Master", + ], + "title": "_Test India Tax Master", "territories": [ { - "doctype": "Applicable Territory", - "parentfield": "territories", + "doctype": "Applicable Territory", + "parentfield": "territories", "territory": "_Test Territory India" } ] - }, + }, { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Master", + "company": "_Test Company", + "doctype": "Sales Taxes and Charges Master", "taxes": [ { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 12 - }, + }, { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", "rate": 4 } - ], - "title": "_Test Sales Taxes and Charges Master - Rest of the World", + ], + "title": "_Test Sales Taxes and Charges Master - Rest of the World", "territories": [ { - "doctype": "Applicable Territory", - "parentfield": "territories", + "doctype": "Applicable Territory", + "parentfield": "territories", "territory": "_Test Territory Rest Of The World" } ] } -] \ No newline at end of file +] diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index a4be3f9ef5b..3d6ba1913c3 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -226,13 +226,13 @@ class calculate_taxes_and_totals(object): # set precision in the last item iteration if n == len(self.doc.get("items")) - 1: self.round_off_totals(tax) - + # adjust Discount Amount loss in last tax iteration if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ and self.doc.discount_amount: self.adjust_discount_amount_loss(tax) - - + + def get_current_tax_amount(self, item, tax, item_tax_map): tax_rate = self._get_tax_rate(tax, item_tax_map) @@ -279,18 +279,23 @@ class calculate_taxes_and_totals(object): tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount + discount_amount_loss, tax.precision("tax_amount")) tax.total = flt(tax.total + discount_amount_loss, tax.precision("total")) - + self._set_in_company_currency(tax, ["total", "tax_amount_after_discount_amount"]) def calculate_totals(self): self.doc.grand_total = flt(self.doc.get("taxes")[-1].total if self.doc.get("taxes") else self.doc.net_total) + self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total, + self.doc.precision("total_taxes_and_charges")) + + self._set_in_company_currency(self.doc, ["total_taxes_and_charges"]) + if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \ if self.doc.total_taxes_and_charges else self.doc.base_net_total else: - self.doc.taxes_and_charges_added, self.taxes_and_charges_deducted = 0.0, 0.0 + self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0 for tax in self.doc.get("taxes"): if tax.category in ["Valuation and Total", "Total"]: if tax.add_deduct_tax == "Add": @@ -306,10 +311,6 @@ class calculate_taxes_and_totals(object): self._set_in_company_currency(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]) - self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total, - self.doc.precision("total_taxes_and_charges")) - - self._set_in_company_currency(self.doc, ["total_taxes_and_charges"]) self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"]) if self.doc.meta.get_field("rounded_total"): @@ -338,20 +339,20 @@ class calculate_taxes_and_totals(object): for i, item in enumerate(self.doc.get("items")): distributed_amount = flt(self.doc.discount_amount) * \ item.net_amount / total_for_discount_amount - + item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount")) net_total += item.net_amount - + # discount amount rounding loss adjustment if no taxes if (not taxes or self.doc.apply_discount_on == "Net Total") \ and i == len(self.doc.get("items")) - 1: - discount_amount_loss = flt(self.doc.total - net_total - self.doc.discount_amount, + discount_amount_loss = flt(self.doc.total - net_total - self.doc.discount_amount, self.doc.precision("net_total")) - item.net_amount = flt(item.net_amount + discount_amount_loss, + item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount")) - + item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) - + self._set_in_company_currency(item, ["net_rate", "net_amount"]) self.discount_amount_applied = True diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index de4dc37a7f7..b6909aa344c 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -124,6 +124,9 @@ class Opportunity(TransactionBase): item_fields = ("item_name", "description", "item_group", "brand") for d in self.items: + if not d.item_code: + continue + item = frappe.db.get_value("Item", d.item_code, item_fields, as_dict=True) for key in item_fields: if not d.get(key): d.set(key, item.get(key)) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 9913054fe75..b1e01610533 100644 --- 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_add, cint +from frappe.utils import getdate, validate_email_add, cint, today from frappe.model.naming import make_autoname from frappe import throw, _, msgprint import frappe.permissions @@ -205,6 +205,6 @@ def send_birthday_reminders(): def get_employees_who_are_born_today(): """Get Employee properties whose birthday is today.""" return frappe.db.sql("""select name, personal_email, company_email, employee_name - from tabEmployee where day(date_of_birth) = day(curdate()) - and month(date_of_birth) = month(curdate()) - and status = 'Active'""", as_dict=True) + from tabEmployee where day(date_of_birth) = day(%(date)s) + and month(date_of_birth) = month(%(date)s) + and status = 'Active'""", {"date": today()}, as_dict=True) diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index a7d48fc6a54..08891e037ad 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -1,99 +1,99 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "creation": "2014-11-27 14:12:07.542534", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Master", + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2014-11-27 14:12:07.542534", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Master", "fields": [ { - "fieldname": "capacity_planning", - "fieldtype": "Section Break", - "label": "Capacity Planning", - "permlevel": 0, + "fieldname": "capacity_planning", + "fieldtype": "Section Break", + "label": "Capacity Planning", + "permlevel": 0, "precision": "" - }, + }, { - "description": "Plan time logs outside Workstation Working Hours.", - "fieldname": "allow_overtime", - "fieldtype": "Check", - "label": "Allow Overtime", - "permlevel": 0, + "description": "Plan time logs outside Workstation Working Hours.", + "fieldname": "allow_overtime", + "fieldtype": "Check", + "label": "Allow Overtime", + "permlevel": 0, "precision": "" - }, + }, { - "default": "", - "fieldname": "allow_production_on_holidays", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Production on Holidays", - "options": "", - "permlevel": 0, + "default": "", + "fieldname": "allow_production_on_holidays", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Production on Holidays", + "options": "", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "default": "30", - "description": "Try planning operations for X days in advance.", - "fieldname": "capacity_planning_for_days", - "fieldtype": "Data", - "label": "Capacity Planning For (Days)", - "permlevel": 0, + "default": "30", + "description": "Try planning operations for X days in advance.", + "fieldname": "capacity_planning_for_days", + "fieldtype": "Data", + "label": "Capacity Planning For (Days)", + "permlevel": 0, "precision": "" - }, + }, { - "description": "Default 10 mins", - "fieldname": "mins_between_operations", - "fieldtype": "Data", - "label": "Time Between Operations (in mins)", - "permlevel": 0, + "description": "Default 10 mins", + "fieldname": "mins_between_operations", + "fieldtype": "Data", + "label": "Time Between Operations (in mins)", + "permlevel": 0, "precision": "" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-wrench", - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "modified": "2015-02-23 23:44:45.917027", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Manufacturing Settings", - "name_case": "", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-wrench", + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "modified": "2015-03-05 23:44:45.917027", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing Settings", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Manufacturing Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", + ], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index aee2a8a3a67..b7fb89d9aff 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -200,7 +200,7 @@ $.extend(cur_frm.cscript, { }, show_time_logs: function(doc, doctype, name) { - frappe.route_options = {"operation_id": name}; + frappe.route_options = {"operation": name}; frappe.set_route("List", "Time Log"); }, diff --git a/erpnext/manufacturing/doctype/production_order/production_order.json b/erpnext/manufacturing/doctype/production_order/production_order.json index bf03b2bf070..53f43f4e83e 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.json +++ b/erpnext/manufacturing/doctype/production_order/production_order.json @@ -1,385 +1,386 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-01-10 16:34:16", - "docstatus": 0, - "doctype": "DocType", + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:16", + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "fieldname": "item", - "fieldtype": "Section Break", - "label": "", - "options": "icon-gift", + "fieldname": "item", + "fieldtype": "Section Break", + "label": "", + "options": "icon-gift", "permlevel": 0 - }, + }, { - "default": "PRO-", - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "PRO-", - "permlevel": 0, + "default": "PRO-", + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "PRO-", + "permlevel": 0, "reqd": 1 - }, + }, { - "default": "Draft", - "depends_on": "eval:!doc.__islocal", - "fieldname": "status", - "fieldtype": "Select", - "in_filter": 1, - "in_list_view": 0, - "label": "Status", - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nIn Process\nCompleted\nCancelled", - "permlevel": 0, - "read_only": 1, - "reqd": 1, + "default": "Draft", + "depends_on": "eval:!doc.__islocal", + "fieldname": "status", + "fieldtype": "Select", + "in_filter": 1, + "in_list_view": 0, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nSubmitted\nStopped\nIn Process\nCompleted\nCancelled", + "permlevel": 0, + "read_only": 1, + "reqd": 1, "search_index": 1 - }, + }, { - "fieldname": "production_item", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "Item To Manufacture", - "oldfieldname": "production_item", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "read_only": 0, + "fieldname": "production_item", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "label": "Item To Manufacture", + "oldfieldname": "production_item", + "oldfieldtype": "Link", + "options": "Item", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "depends_on": "", - "description": "", - "fieldname": "bom_no", - "fieldtype": "Link", - "in_list_view": 0, - "label": "BOM No", - "oldfieldname": "bom_no", - "oldfieldtype": "Link", - "options": "BOM", - "permlevel": 0, - "read_only": 0, + "depends_on": "", + "description": "", + "fieldname": "bom_no", + "fieldtype": "Link", + "in_list_view": 0, + "label": "BOM No", + "oldfieldname": "bom_no", + "oldfieldtype": "Link", + "options": "BOM", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "default": "1", - "description": "Plan material for sub-assemblies", - "fieldname": "use_multi_level_bom", - "fieldtype": "Check", - "label": "Use Multi-Level BOM", + "default": "1", + "description": "Plan material for sub-assemblies", + "fieldname": "use_multi_level_bom", + "fieldtype": "Check", + "label": "Use Multi-Level BOM", "permlevel": 0 - }, + }, { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "permlevel": 0, - "read_only": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "permlevel": 0, + "read_only": 0, "width": "50%" - }, + }, { - "depends_on": "", - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 0, - "label": "Qty To Manufacture", - "oldfieldname": "qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "read_only": 0, + "depends_on": "", + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 0, + "label": "Qty To Manufacture", + "oldfieldname": "qty", + "oldfieldtype": "Currency", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "description": "", - "fieldname": "material_transferred_for_qty", - "fieldtype": "Int", - "label": "Material Transferred for Qty", - "permlevel": 0, - "precision": "", + "description": "", + "fieldname": "material_transferred_for_qty", + "fieldtype": "Int", + "label": "Material Transferred for Qty", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "default": "0", - "depends_on": "eval:doc.docstatus==1", - "description": "", - "fieldname": "produced_qty", - "fieldtype": "Float", - "label": "Manufactured Qty", - "no_copy": 1, - "oldfieldname": "produced_qty", - "oldfieldtype": "Currency", - "permlevel": 0, + "default": "0", + "depends_on": "eval:doc.docstatus==1", + "description": "", + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Manufactured Qty", + "no_copy": 1, + "oldfieldname": "produced_qty", + "oldfieldtype": "Currency", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "warehouses", - "fieldtype": "Section Break", - "label": "Warehouses", - "options": "icon-building", + "fieldname": "warehouses", + "fieldtype": "Section Break", + "label": "Warehouses", + "options": "icon-building", "permlevel": 0 - }, + }, { - "fieldname": "wip_warehouse", - "fieldtype": "Link", - "label": "Work-in-Progress Warehouse", - "options": "Warehouse", - "permlevel": 0, + "fieldname": "wip_warehouse", + "fieldtype": "Link", + "label": "Work-in-Progress Warehouse", + "options": "Warehouse", + "permlevel": 0, "reqd": 0 - }, + }, { - "fieldname": "column_break_12", - "fieldtype": "Column Break", + "fieldname": "column_break_12", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "depends_on": "", - "description": "", - "fieldname": "fg_warehouse", - "fieldtype": "Link", - "in_list_view": 0, - "label": "Target Warehouse", - "options": "Warehouse", - "permlevel": 0, - "read_only": 0, + "depends_on": "", + "description": "", + "fieldname": "fg_warehouse", + "fieldtype": "Link", + "in_list_view": 0, + "label": "Target Warehouse", + "options": "Warehouse", + "permlevel": 0, + "read_only": 0, "reqd": 0 - }, + }, { - "fieldname": "time", - "fieldtype": "Section Break", - "label": "Time", - "options": "icon-time", - "permlevel": 0, + "fieldname": "time", + "fieldtype": "Section Break", + "label": "Time", + "options": "icon-time", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "", - "fieldname": "expected_delivery_date", - "fieldtype": "Date", - "label": "Expected Delivery Date", - "permlevel": 0, + "depends_on": "", + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "label": "Expected Delivery Date", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "planned_start_date", - "fieldtype": "Datetime", - "label": "Planned Start Date", - "permlevel": 0, + "default": "now", + "fieldname": "planned_start_date", + "fieldtype": "Datetime", + "label": "Planned Start Date", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "planned_end_date", - "fieldtype": "Datetime", - "label": "Planned End Date", - "permlevel": 0, + "fieldname": "planned_end_date", + "fieldtype": "Datetime", + "label": "Planned End Date", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_13", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "actual_start_date", - "fieldtype": "Datetime", - "label": "Actual Start Date", - "permlevel": 0, - "precision": "", + "fieldname": "actual_start_date", + "fieldtype": "Datetime", + "label": "Actual Start Date", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "actual_end_date", - "fieldtype": "Datetime", - "label": "Actual End Date", - "permlevel": 0, - "precision": "", + "fieldname": "actual_end_date", + "fieldtype": "Datetime", + "label": "Actual End Date", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "depends_on": "", - "fieldname": "operations_section", - "fieldtype": "Section Break", - "label": "Operations", - "options": "icon-wrench", - "permlevel": 0, + "depends_on": "", + "fieldname": "operations_section", + "fieldtype": "Section Break", + "label": "Operations", + "options": "icon-wrench", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "operations", - "fieldtype": "Table", - "label": "Operations", - "options": "Production Order Operation", - "permlevel": 0, - "precision": "", + "fieldname": "operations", + "fieldtype": "Table", + "label": "Operations", + "options": "Production Order Operation", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "section_break_22", - "fieldtype": "Section Break", - "label": "Operation Cost", - "options": "", - "permlevel": 0, + "fieldname": "section_break_22", + "fieldtype": "Section Break", + "label": "Operation Cost", + "options": "", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "planned_operating_cost", - "fieldtype": "Currency", - "label": "Planned Operating Cost", - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", + "fieldname": "planned_operating_cost", + "fieldtype": "Currency", + "label": "Planned Operating Cost", + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "actual_operating_cost", - "fieldtype": "Currency", - "label": "Actual Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", + "fieldname": "actual_operating_cost", + "fieldtype": "Currency", + "label": "Actual Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "additional_operating_cost", - "fieldtype": "Currency", - "label": "Additional Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, + "fieldname": "additional_operating_cost", + "fieldtype": "Currency", + "label": "Additional Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_24", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_24", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "total_operating_cost", - "fieldtype": "Currency", - "label": "Total Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", + "fieldname": "total_operating_cost", + "fieldtype": "Currency", + "label": "Total Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Info", - "options": "icon-file-text", - "permlevel": 0, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Info", + "options": "icon-file-text", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Item Description", - "permlevel": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Item Description", + "permlevel": 0, "read_only": 1 - }, + }, { - "depends_on": "", - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, + "depends_on": "", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "column_break2", - "fieldtype": "Column Break", - "permlevel": 0, - "read_only": 0, + "fieldname": "column_break2", + "fieldtype": "Column Break", + "permlevel": 0, + "read_only": 0, "width": "50%" - }, + }, { - "fieldname": "project_name", - "fieldtype": "Link", - "in_filter": 1, - "label": "Project Name", - "oldfieldname": "project_name", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0, + "fieldname": "project_name", + "fieldtype": "Link", + "in_filter": 1, + "label": "Project Name", + "oldfieldname": "project_name", + "oldfieldtype": "Link", + "options": "Project", + "permlevel": 0, "read_only": 0 - }, + }, { - "description": "Manufacture against Sales Order", - "fieldname": "sales_order", - "fieldtype": "Link", - "label": "Sales Order", - "options": "Sales Order", - "permlevel": 0, + "description": "Manufacture against Sales Order", + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "options": "Sales Order", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "read_only": 0, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Production Order", - "permlevel": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Production Order", + "permlevel": 0, "read_only": 1 } - ], - "icon": "icon-cogs", - "idx": 1, - "in_create": 0, - "is_submittable": 1, - "modified": "2015-02-26 04:03:28.164713", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Production Order", - "owner": "Administrator", + ], + "icon": "icon-cogs", + "idx": 1, + "in_create": 0, + "is_submittable": 1, + "modified": "2015-03-05 13:03:28.164713", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Order", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "apply_user_permissions": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "share": 1, - "submit": 1, + "amend": 1, + "apply_user_permissions": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "apply_user_permissions": 1, - "permlevel": 0, - "read": 1, - "report": 1, + "apply_user_permissions": 1, + "permlevel": 0, + "read": 1, + "report": 1, "role": "Material User" } - ], + ], "title_field": "production_item" -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index cb9b58429e7..e4a7292015a 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -4,12 +4,11 @@ from __future__ import unicode_literals import frappe, json -from frappe.utils import flt, nowdate, get_datetime, getdate, date_diff, time_diff_in_seconds +from frappe.utils import flt, nowdate, get_datetime, getdate, date_diff from frappe import _ from frappe.model.document import Document from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from dateutil.relativedelta import relativedelta -from dateutil.parser import parse class OverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass @@ -64,7 +63,7 @@ class ProductionOrder(Document): def calculate_operating_cost(self): self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 for d in self.get("operations"): - d.actual_operating_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 + d.actual_operating_cost = flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0) self.planned_operating_cost += flt(d.planned_operating_cost) self.actual_operating_cost += flt(d.actual_operating_cost) @@ -273,16 +272,8 @@ class ProductionOrder(Document): def check_operation_fits_in_working_hours(self, d): """Raises expection if operation is longer than working hours in the given workstation.""" - operation_length = time_diff_in_seconds(d.planned_end_time, d.planned_start_time) - - workstation = frappe.get_doc("Workstation", d.workstation) - for working_hour in workstation.working_hours: - slot_length = (parse(working_hour.end_time) - parse(working_hour.start_time)).total_seconds() - if slot_length >= operation_length: - return - - frappe.throw(_("Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations").format(d.operation, d.workstation), - OperationTooLongError) + from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours + check_if_within_operating_hours(d.workstation, d.operation, d.planned_start_time, d.planned_end_time) def update_operation_status(self): for d in self.get("operations"): @@ -386,10 +377,11 @@ def make_time_log(name, operation, from_time, to_time, qty=None, project=None, time_log.production_order = name time_log.project = project time_log.operation_id = operation_id - time_log.operation= operation + time_log.operation = operation time_log.workstation= workstation time_log.activity_type= "Manufacturing" time_log.completed_qty = flt(qty) + if from_time and to_time : time_log.calculate_total_hours() return time_log diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index 4fb7d24f09e..37df0814320 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import unittest import frappe +from frappe.utils import flt, get_datetime from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry from erpnext.stock.doctype.stock_entry import test_stock_entry -from erpnext.projects.doctype.time_log.time_log import OverProductionError +from erpnext.projects.doctype.time_log.time_log import OverProductionLoggedError class TestProductionOrder(unittest.TestCase): def check_planned_qty(self): @@ -27,7 +28,7 @@ class TestProductionOrder(unittest.TestCase): target="Stores - _TC", qty=100, incoming_rate=100) # from stores to wip - s = frappe.get_doc(make_stock_entry(pro_doc.name, "Material Transfer", 4)) + s = frappe.get_doc(make_stock_entry(pro_doc.name, "Material Transfer for Manufacture", 4)) for d in s.get("items"): d.s_warehouse = "Stores - _TC" s.fiscal_year = "_Test Fiscal Year 2013" @@ -45,6 +46,7 @@ class TestProductionOrder(unittest.TestCase): self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name, "produced_qty"), 4) planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") + self.assertEqual(planned1 - planned0, 6) return pro_doc @@ -66,38 +68,34 @@ class TestProductionOrder(unittest.TestCase): self.assertRaises(StockOverProductionError, s.submit) def test_make_time_log(self): + from erpnext.manufacturing.doctype.production_order.production_order import make_time_log + from frappe.utils import cstr + from frappe.utils import time_diff_in_hours + prod_order = frappe.get_doc({ "doctype": "Production Order", "production_item": "_Test FG Item 2", "bom_no": "BOM/_Test FG Item 2/002", "qty": 1, "wip_warehouse": "_Test Warehouse - _TC", - "fg_warehouse": "_Test Warehouse 1 - _TC" + "fg_warehouse": "_Test Warehouse 1 - _TC", + "company": "_Test Company", + "planned_start_date": "2014-11-25 00:00:00" }) - prod_order.set_production_order_operations() - prod_order.operations[0].update({ - "planned_start_time": "2014-11-25 00:00:00", - "planned_end_time": "2014-11-25 10:00:00", - "hour_rate": 10 - }) - prod_order.insert() - + prod_order.submit() d = prod_order.operations[0] - from erpnext.manufacturing.doctype.production_order.production_order import make_time_log - from frappe.utils import cstr - from frappe.utils import time_diff_in_hours + d.completed_qty = flt(d.completed_qty) - prod_order.submit() - - time_log = make_time_log( prod_order.name, cstr(d.idx) + ". " + d.operation, \ - d.planned_start_time, d.planned_end_time, prod_order.qty - d.qty_completed) + time_log = make_time_log(prod_order.name, cstr(d.idx) + ". " + d.operation, \ + d.planned_start_time, d.planned_end_time, prod_order.qty - d.completed_qty, + operation_id=d.name) self.assertEqual(prod_order.name, time_log.production_order) - self.assertEqual((prod_order.qty - d.qty_completed), time_log.qty) + self.assertEqual((prod_order.qty - d.completed_qty), time_log.completed_qty) self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours) time_log.save() @@ -105,7 +103,6 @@ class TestProductionOrder(unittest.TestCase): manufacturing_settings = frappe.get_doc({ "doctype": "Manufacturing Settings", - "maximum_overtime": 30, "allow_production_on_holidays": 0 }) @@ -113,30 +110,30 @@ class TestProductionOrder(unittest.TestCase): prod_order.load_from_db() self.assertEqual(prod_order.operations[0].status, "Completed") - self.assertEqual(prod_order.operations[0].qty_completed, prod_order.qty) + self.assertEqual(prod_order.operations[0].completed_qty, prod_order.qty) - self.assertEqual(prod_order.operations[0].actual_start_time, time_log.from_time) - self.assertEqual(prod_order.operations[0].actual_end_time, time_log.to_time) + self.assertEqual(get_datetime(prod_order.operations[0].actual_start_time), get_datetime(time_log.from_time)) + self.assertEqual(get_datetime(prod_order.operations[0].actual_end_time), get_datetime(time_log.to_time)) - self.assertEqual(prod_order.operations[0].actual_operation_time, 600) - self.assertEqual(prod_order.operations[0].actual_operating_cost, 6000) + self.assertEqual(prod_order.operations[0].actual_operation_time, 60) + self.assertEqual(prod_order.operations[0].actual_operating_cost, 100) time_log.cancel() prod_order.load_from_db() self.assertEqual(prod_order.operations[0].status, "Pending") - self.assertEqual(prod_order.operations[0].qty_completed, 0) + self.assertEqual(flt(prod_order.operations[0].completed_qty), 0) - self.assertEqual(prod_order.operations[0].actual_operation_time, 0) - self.assertEqual(prod_order.operations[0].actual_operating_cost, 0) + self.assertEqual(flt(prod_order.operations[0].actual_operation_time), 0) + self.assertEqual(flt(prod_order.operations[0].actual_operating_cost), 0) time_log2 = frappe.copy_doc(time_log) time_log2.update({ - "qty": 10, + "completed_qty": 10, "from_time": "2014-11-26 00:00:00", "to_time": "2014-11-26 00:00:00", "docstatus": 0 }) - self.assertRaises(OverProductionError, time_log2.save) + self.assertRaises(OverProductionLoggedError, time_log2.save) test_records = frappe.get_test_records('Production Order') diff --git a/erpnext/manufacturing/doctype/workstation/test_records.json b/erpnext/manufacturing/doctype/workstation/test_records.json index ffc1ec84432..6d54cb29127 100644 --- a/erpnext/manufacturing/doctype/workstation/test_records.json +++ b/erpnext/manufacturing/doctype/workstation/test_records.json @@ -4,7 +4,10 @@ "name": "_Test Workstation 1", "workstation_name": "_Test Workstation 1", "warehouse": "_Test warehouse - _TC", - "hour_rate":100, + "hour_rate_labour": 25, + "hour_rate_electricity": 25, + "hour_rate_consumable": 25, + "hour_rate_rent": 25, "holiday_list": "_Test Holiday List", "working_hours": [ { diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index 747acf94dd1..d73332ddba7 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import unittest +from .workstation import check_if_within_operating_hours, NotInWorkingHoursError, WorkstationHolidayError test_dependencies = ["Warehouse"] test_records = frappe.get_test_records('Workstation') @@ -11,6 +12,11 @@ test_records = frappe.get_test_records('Workstation') class TestWorkstation(unittest.TestCase): def test_validate_timings(self): - wks = frappe.get_doc("Workstation", "_Test Workstation 1") - self.assertEqual(1,wks.check_workstation_for_operation_time("2013-02-01 05:00:00", "2013-02-02 20:00:00")) - self.assertEqual(None,wks.check_workstation_for_operation_time("2013-02-03 10:00:00", "2013-02-03 20:00:00")) + check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00") + check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00") + self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours, + "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00") + self.assertRaises(NotInWorkingHoursError, check_if_within_operating_hours, + "_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00") + self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours, + "_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00") diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index 9ca88c52ec8..46ac74dc896 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -4,29 +4,21 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint, getdate, formatdate, comma_and, get_datetime - +from frappe.utils import flt, cint, getdate, formatdate, comma_and, time_diff_in_seconds from frappe.model.document import Document +from dateutil.parser import parse class WorkstationHolidayError(frappe.ValidationError): pass class NotInWorkingHoursError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass class Workstation(Document): - def update_bom_operation(self): - bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation` - where workstation = %s""", self.name) - for bom_no in bom_list: - frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s - where parent = %s and workstation = %s""", - (self.hour_rate, bom_no[0], self.name)) + def validate(self): + self.hour_rate = (flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) + + flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) def on_update(self): self.validate_overlap_for_operation_timings() - - frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) + - flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) - self.update_bom_operation() def validate_overlap_for_operation_timings(self): @@ -43,32 +35,35 @@ class Workstation(Document): if existing: frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) + def update_bom_operation(self): + bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation` + where workstation = %s""", self.name) + for bom_no in bom_list: + frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s + where parent = %s and workstation = %s""", + (self.hour_rate, bom_no[0], self.name)) + @frappe.whitelist() def get_default_holiday_list(): return frappe.db.get_value("Company", frappe.defaults.get_user_default("company"), "default_holiday_list") -def check_if_within_operating_hours(workstation, from_datetime, to_datetime): - if not cint(frappe.db.get_value("Manufacturing Settings", None, "allow_overtime")): - is_within_operating_hours(workstation, from_datetime, to_datetime) - +def check_if_within_operating_hours(workstation, operation, from_datetime, to_datetime): if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")): check_workstation_for_holiday(workstation, from_datetime, to_datetime) -def is_within_operating_hours(workstation, from_datetime, to_datetime): - start_time = get_datetime(from_datetime).time() - end_time = get_datetime(to_datetime).time() + if not cint(frappe.db.get_value("Manufacturing Settings", None, "allow_overtime")): + is_within_operating_hours(workstation, operation, from_datetime, to_datetime) - working_hours = frappe.db.sql_list("""select idx from `tabWorkstation Working Hour` - where parent = %s - and ( - (start_time between %s and %s) or - (end_time between %s and %s) or - (%s between start_time and end_time)) - """, (workstation, start_time, end_time, start_time, end_time, start_time)) +def is_within_operating_hours(workstation, operation, from_datetime, to_datetime): + operation_length = time_diff_in_seconds(to_datetime, from_datetime) + workstation = frappe.get_doc("Workstation", workstation) - if not working_hours: - frappe.throw(_("Time Log timings outside workstation operating hours"), NotInWorkingHoursError) + for working_hour in workstation.working_hours: + slot_length = (parse(working_hour.end_time) - parse(working_hour.start_time)).total_seconds() + if slot_length >= operation_length: + return + frappe.throw(_("Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations").format(operation, workstation.name), NotInWorkingHoursError) def check_workstation_for_holiday(workstation, from_datetime, to_datetime): holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") diff --git a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json index a513cdee8fc..4ca59364348 100644 --- a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json +++ b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json @@ -1,129 +1,129 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "creation": "2014-12-24 14:46:40.678236", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2014-12-24 14:46:40.678236", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", "fields": [ { - "allow_on_submit": 0, - "fieldname": "start_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Start Time", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "fieldname": "start_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Start Time", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "fieldname": "end_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "End Time", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "fieldname": "end_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "End Time", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "fieldname": "section_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "fieldname": "section_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "default": "1", - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Enabled", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Enabled", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "modified": "2015-02-11 14:55:55.650726", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Workstation Working Hour", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-03-05 14:55:55.650726", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Workstation Working Hour", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 9042c830533..744d6b427b5 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -4,4 +4,5 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Project') \ No newline at end of file +test_records = frappe.get_test_records('Project') +test_ignore = ["Task"] diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 186fb41d874..6f75e0deb84 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Task') - -test_ignore = ["Customer"] \ No newline at end of file +test_dependencies = ["Project"] +test_ignore = ["Customer"] diff --git a/erpnext/projects/doctype/time_log/test_records.json b/erpnext/projects/doctype/time_log/test_records.json index 0ef276ecbe7..077071ce087 100644 --- a/erpnext/projects/doctype/time_log/test_records.json +++ b/erpnext/projects/doctype/time_log/test_records.json @@ -1,10 +1,12 @@ [ { - "activity_type": "_Test Activity Type", - "docstatus": 1, - "doctype": "Time Log", - "from_time": "2013-01-01 10:00:00.000000", - "note": "_Test Note", - "to_time": "2013-01-01 11:00:00.000000" + "activity_type": "_Test Activity Type", + "docstatus": 1, + "doctype": "Time Log", + "from_time": "2013-01-01 10:00:00.000000", + "note": "_Test Note", + "to_time": "2013-01-01 11:00:00.000000", + "time_log_for": "Project", + "project": "_Test Project" } -] \ No newline at end of file +] diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 9d4e37f9d11..6b04132d6cd 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -10,17 +10,22 @@ from erpnext.projects.doctype.time_log.time_log import OverlapError from erpnext.projects.doctype.time_log.time_log import NotSubmittedError from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError -from erpnext.manufacturing.doctype.workstation.workstation import WorkstationIsClosedError +from erpnext.manufacturing.doctype.workstation.workstation import NotInWorkingHoursError from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * class TestTimeLog(unittest.TestCase): def test_duplication(self): frappe.db.sql("delete from `tabTime Log`") - frappe.get_doc(frappe.copy_doc(test_records[0])).insert() - ts = frappe.get_doc(frappe.copy_doc(test_records[0])) - self.assertRaises(OverlapError, ts.insert) + tl1 = frappe.get_doc(frappe.copy_doc(test_records[0])) + tl1.user = "test@example.com" + tl1.insert() + + tl2 = frappe.get_doc(frappe.copy_doc(test_records[0])) + tl2.user = "test@example.com" + + self.assertRaises(OverlapError, tl2.insert) frappe.db.sql("delete from `tabTime Log`") @@ -42,7 +47,7 @@ class TestTimeLog(unittest.TestCase): def test_time_log_on_holiday(self): prod_order = make_prod_order(self) - + prod_order.set_production_order_operations() prod_order.save() prod_order.submit() @@ -50,7 +55,10 @@ class TestTimeLog(unittest.TestCase): "doctype": "Time Log", "time_log_for": "Manufacturing", "production_order": prod_order.name, + "operation": prod_order.operations[0].operation, + "operation_id": prod_order.operations[0].name, "qty": 1, + "activity_type": "_Test Activity Type", "from_time": "2013-02-01 10:00:00", "to_time": "2013-02-01 20:00:00", "workstation": "_Test Workstation 1" @@ -61,9 +69,9 @@ class TestTimeLog(unittest.TestCase): "from_time": "2013-02-02 09:00:00", "to_time": "2013-02-02 20:00:00" }) - self.assertRaises(WorkstationIsClosedError , time_log.save) + self.assertRaises(NotInWorkingHoursError , time_log.save) - time_log.from_time= "2013-02-02 09:30:00" + time_log.from_time= "2013-02-02 10:30:00" time_log.save() time_log.submit() time_log.cancel() @@ -84,7 +92,7 @@ def make_prod_order(self): "bom_no": "BOM/_Test FG Item 2/002", "qty": 1, "wip_warehouse": "_Test Warehouse - _TC", - "fg_warehouse": "_Test Warehouse 1 - _TC" + "fg_warehouse": "_Test Warehouse 1 - _TC", }) test_records = frappe.get_test_records('Time Log') diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 2316cc8f160..5ab2efa922b 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -1,283 +1,283 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-04-03 16:38:41", - "description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Master", + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-04-03 16:38:41", + "description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Master", "fields": [ { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "TL-", - "permlevel": 0, - "read_only": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "TL-", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "fieldname": "from_time", - "fieldtype": "Datetime", - "in_list_view": 0, - "label": "From Time", - "permlevel": 0, - "read_only": 0, + "fieldname": "from_time", + "fieldtype": "Datetime", + "in_list_view": 0, + "label": "From Time", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "fieldname": "to_time", - "fieldtype": "Datetime", - "in_list_view": 0, - "label": "To Time", - "permlevel": 0, - "read_only": 0, + "fieldname": "to_time", + "fieldtype": "Datetime", + "in_list_view": 0, + "label": "To Time", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "fieldname": "hours", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Hours", - "permlevel": 0, + "fieldname": "hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Hours", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "billable", - "fieldtype": "Check", - "in_list_view": 0, - "label": "Billable", - "permlevel": 0, + "fieldname": "billable", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Billable", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User", - "permlevel": 0, + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 0, - "label": "Status", - "options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled", - "permlevel": 0, - "read_only": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 0, + "label": "Status", + "options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled", + "permlevel": 0, + "read_only": 1, "reqd": 0 - }, + }, { - "default": "Project", - "fieldname": "time_log_for", - "fieldtype": "Select", - "label": "Time Log For", - "options": "\nProject\nManufacturing", - "permlevel": 0, - "precision": "", - "read_only": 1, + "default": "Project", + "fieldname": "time_log_for", + "fieldtype": "Select", + "label": "Time Log For", + "options": "Project\nManufacturing", + "permlevel": 0, + "precision": "", + "read_only": 1, "reqd": 0 - }, + }, { - "depends_on": "", - "fieldname": "activity_type", - "fieldtype": "Link", - "in_list_view": 0, - "label": "Activity Type", - "options": "Activity Type", - "permlevel": 0, - "read_only": 0, + "depends_on": "", + "fieldname": "activity_type", + "fieldtype": "Link", + "in_list_view": 0, + "label": "Activity Type", + "options": "Activity Type", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "depends_on": "eval:doc.time_log_for != 'Manufacturing'", - "fieldname": "task", - "fieldtype": "Link", - "label": "Task", - "options": "Task", - "permlevel": 0, + "depends_on": "eval:doc.time_log_for != 'Manufacturing'", + "fieldname": "task", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "permlevel": 0, "read_only": 0 - }, + }, { - "depends_on": "eval:doc.time_log_for == 'Manufacturing'", - "fieldname": "section_break_11", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "eval:doc.time_log_for == 'Manufacturing'", - "fieldname": "production_order", - "fieldtype": "Link", - "label": "Production Order", - "options": "Production Order", - "permlevel": 0, - "precision": "", + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "production_order", + "fieldtype": "Link", + "label": "Production Order", + "options": "Production Order", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "depends_on": "eval:doc.time_log_for == 'Manufacturing'", - "fieldname": "operation", - "fieldtype": "Select", - "label": "Operation", - "options": "", - "permlevel": 0, - "precision": "", + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "operation", + "fieldtype": "Select", + "label": "Operation", + "options": "", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "operation_id", - "fieldtype": "Data", - "label": "Operation ID", - "options": "", - "permlevel": 0, - "precision": "", + "fieldname": "operation_id", + "fieldtype": "Data", + "label": "Operation ID", + "options": "", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "eval:doc.time_log_for == 'Manufacturing'", - "fieldname": "workstation", - "fieldtype": "Link", - "label": "Workstation", - "options": "Workstation", - "permlevel": 0, - "precision": "", + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "workstation", + "fieldtype": "Link", + "label": "Workstation", + "options": "Workstation", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "depends_on": "eval:doc.time_log_for == 'Manufacturing'", - "description": "Operation completed for how many finished goods?", - "fieldname": "completed_qty", - "fieldtype": "Float", - "label": "Completed Qty", - "permlevel": 0, + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "description": "Operation completed for how many finished goods?", + "fieldname": "completed_qty", + "fieldtype": "Float", + "label": "Completed Qty", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "permlevel": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "note", - "fieldtype": "Text Editor", - "label": "Note", - "permlevel": 0, + "fieldname": "note", + "fieldtype": "Text Editor", + "label": "Note", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "permlevel": 0, + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "permlevel": 0, "read_only": 0 - }, + }, { - "depends_on": "eval:doc.time_log_for", - "fieldname": "project", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Project", - "options": "Project", - "permlevel": 0, + "depends_on": "eval:doc.time_log_for", + "fieldname": "project", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Project", + "options": "Project", + "permlevel": 0, "read_only": 0 - }, + }, { - "description": "Will be updated when batched.", - "fieldname": "time_log_batch", - "fieldtype": "Link", - "label": "Time Log Batch", - "options": "Time Log Batch", - "permlevel": 0, + "description": "Will be updated when batched.", + "fieldname": "time_log_batch", + "fieldtype": "Link", + "label": "Time Log Batch", + "options": "Time Log Batch", + "permlevel": 0, "read_only": 1 - }, + }, { - "description": "Will be updated when billed.", - "fieldname": "sales_invoice", - "fieldtype": "Link", - "label": "Sales Invoice", - "options": "Sales Invoice", - "permlevel": 0, + "description": "Will be updated when billed.", + "fieldname": "sales_invoice", + "fieldtype": "Link", + "label": "Sales Invoice", + "options": "Sales Invoice", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "permlevel": 0, "read_only": 0 - }, + }, { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Time Log", - "permlevel": 1, - "print_hide": 1, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Time Log", + "permlevel": 1, + "print_hide": 1, "read_only": 0 - }, + }, { - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "permlevel": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "permlevel": 0, "precision": "" } - ], - "icon": "icon-time", - "idx": 1, - "is_submittable": 1, - "modified": "2015-02-26 02:22:10.312376", - "modified_by": "Administrator", - "module": "Projects", - "name": "Time Log", - "owner": "Administrator", + ], + "icon": "icon-time", + "idx": 1, + "is_submittable": 1, + "modified": "2015-02-26 02:22:10.312376", + "modified_by": "Administrator", + "module": "Projects", + "name": "Time Log", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "apply_user_permissions": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "share": 1, - "submit": 1, + "amend": 1, + "apply_user_permissions": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "delete": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects Manager", - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "submit": 1, "write": 1 } - ], + ], "title_field": "title" -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 3e7d667aa6f..36aac548097 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -9,7 +9,7 @@ from dateutil.relativedelta import relativedelta from dateutil.parser import parse class OverlapError(frappe.ValidationError): pass -class OverProductionError(frappe.ValidationError): pass +class OverProductionLoggedError(frappe.ValidationError): pass class NotSubmittedError(frappe.ValidationError): pass from frappe.model.document import Document @@ -76,17 +76,22 @@ class TimeLog(Document): def get_overlap_for(self, fieldname): if not self.get(fieldname): return - existing = frappe.db.sql("""select name, from_time, to_time from `tabTime Log` where `{0}`=%s and + + existing = frappe.db.sql("""select name, from_time, to_time from `tabTime Log` where `{0}`=%(val)s and ( - (from_time between %s and %s) or - (to_time between %s and %s) or - (%s between from_time and to_time)) - and name!=%s - and ifnull(task, "")=%s + (from_time between %(from_time)s and %(to_time)s) or + (to_time between %(from_time)s and %(to_time)s) or + (%(from_time)s between from_time and to_time)) + and name!=%(name)s + and ifnull(task, "")=%(task)s and docstatus < 2""".format(fieldname), - (self.get(fieldname), self.from_time, self.to_time, self.from_time, - self.to_time, self.from_time, self.name or "No Name", - cstr(self.task)), as_dict=True) + { + "val": self.get(fieldname), + "from_time": self.from_time, + "to_time": self.to_time, + "name": self.name or "No Name", + "task": cstr(self.task) + }, as_dict=True) return existing[0] if existing else None @@ -107,7 +112,7 @@ class TimeLog(Document): """Checks if **Time Log** is between operating hours of the **Workstation**.""" if self.workstation: from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours - check_if_within_operating_hours(self.workstation, self.from_time, self.to_time) + check_if_within_operating_hours(self.workstation, self.operation, self.from_time, self.to_time) def validate_production_order(self): """Throws 'NotSubmittedError' if **production order** is not submitted. """ @@ -194,7 +199,14 @@ class TimeLog(Document): if not self.operation: frappe.throw(_("Operation is Mandatory")) if not self.completed_qty: - self.completed_qty=0 + self.completed_qty = 0 + + production_order = frappe.get_doc("Production Order", self.production_order) + pending_qty = flt(production_order.qty) - flt(production_order.produced_qty) + if flt(self.completed_qty) > pending_qty: + frappe.throw(_("Completed Qty cannot be more than {0} for operation {1}").format(pending_qty, self.operation), + OverProductionLoggedError) + else: self.production_order = None self.operation = None diff --git a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py index 44a587d63a9..51a3240f450 100644 --- a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py +++ b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py @@ -34,7 +34,8 @@ def create_time_log(): time_log.update({ "from_time": "2013-01-02 10:00:00.000000", "to_time": "2013-01-02 11:00:00.000000", - "docstatus": 0 + "docstatus": 0, + "time_log_for": "Project" }) time_log.insert() time_log.submit() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 547ead60485..e41f39e5972 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -469,7 +469,7 @@ class StockEntry(StockController): pro_doc = frappe.get_doc("Production Order", self.production_order) _validate_production_order(pro_doc) pro_doc.run_method("update_status") - if self.purpose in ("Manufacture", "Material Transfer for Manufacture"): + if self.purpose in "Manufacture": pro_doc.run_method("update_production_order_qty") self.update_planned_qty(pro_doc) @@ -543,7 +543,7 @@ class StockEntry(StockController): def get_items(self): if not self.fg_completed_qty or not self.bom_no: frappe.throw(_("BOM and Manufacturing Quantity are required")) - + self.set('items', []) self.validate_production_order()