Fixed Test Cases

This commit is contained in:
Anand Doshi 2015-03-05 19:31:23 +05:30
parent 06a6bcce17
commit ec5ec60764
27 changed files with 1005 additions and 972 deletions

View File

@ -29,6 +29,7 @@ def _make_test_records(verbose):
["_Test Account S&H Education Cess", "_Test Account Tax Assets", "Ledger", "Tax"], ["_Test Account S&H Education Cess", "_Test Account Tax Assets", "Ledger", "Tax"],
["_Test Account CST", "Direct Expenses", "Ledger", "Tax"], ["_Test Account CST", "Direct Expenses", "Ledger", "Tax"],
["_Test Account Discount", "Direct Expenses", "Ledger", None], ["_Test Account Discount", "Direct Expenses", "Ledger", None],
["_Test Write Off", "Indirect Expenses", "Ledger", None],
# related to Account Inventory Integration # related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", "Ledger", None], ["_Test Account Stock In Hand", "Current Assets", "Ledger", None],

View File

@ -2,25 +2,32 @@
{ {
"budgets": [ "budgets": [
{ {
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"budget_allocated": 100000, "budget_allocated": 100000,
"doctype": "Budget Detail", "doctype": "Budget Detail",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",
"parentfield": "budgets" "parentfield": "budgets"
} }
], ],
"company": "_Test Company", "company": "_Test Company",
"cost_center_name": "_Test Cost Center", "cost_center_name": "_Test Cost Center",
"distribution_id": "_Test Distribution", "distribution_id": "_Test Distribution",
"doctype": "Cost Center", "doctype": "Cost Center",
"group_or_ledger": "Ledger", "group_or_ledger": "Ledger",
"parent_cost_center": "_Test Company - _TC" "parent_cost_center": "_Test Company - _TC"
}, },
{ {
"company": "_Test Company", "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", "doctype": "Cost Center",
"group_or_ledger": "Ledger", "group_or_ledger": "Ledger",
"parent_cost_center": "_Test Company - _TC" "parent_cost_center": "_Test Company - _TC"
} }
] ]

View File

@ -15,6 +15,7 @@ class TestPricingRule(unittest.TestCase):
test_record = { test_record = {
"doctype": "Pricing Rule", "doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code", "apply_on": "Item Code",
"item_code": "_Test Item", "item_code": "_Test Item",
"selling": 1, "selling": 1,

View File

@ -53,7 +53,7 @@
"description": "Shipping Charges", "description": "Shipping Charges",
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 100 "tax_amount": 100
}, },
{ {
"account_head": "_Test Account Customs Duty - _TC", "account_head": "_Test Account Customs Duty - _TC",
@ -176,7 +176,7 @@
"description": "Shipping Charges", "description": "Shipping Charges",
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 100.0 "tax_amount": 100.0
}, },
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
@ -187,7 +187,7 @@
"description": "VAT", "description": "VAT",
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 120.0 "tax_amount": 120.0
}, },
{ {
"account_head": "_Test Account Customs Duty - _TC", "account_head": "_Test Account Customs Duty - _TC",
@ -198,7 +198,7 @@
"description": "Customs Duty", "description": "Customs Duty",
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 150.0 "tax_amount": 150.0
} }
], ],
"posting_date": "2013-02-03", "posting_date": "2013-02-03",

View File

@ -174,7 +174,7 @@
"description": "Shipping Charges", "description": "Shipping Charges",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 100 "tax_amount": 100
}, },
{ {
"account_head": "_Test Account Customs Duty - _TC", "account_head": "_Test Account Customs Duty - _TC",
@ -367,7 +367,7 @@
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"idx": 7, "idx": 7,
"parentfield": "taxes", "parentfield": "taxes",
"rate": 100 "tax_amount": 100
}, },
{ {
"account_head": "_Test Account Discount - _TC", "account_head": "_Test Account Discount - _TC",

View File

@ -254,9 +254,9 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"],
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 499.98], "_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, 150, 150, 750], "_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 # check if children are saved
@ -500,7 +500,9 @@ class TestSalesInvoice(unittest.TestCase):
"naming_series": "_T-POS Setting-", "naming_series": "_T-POS Setting-",
"selling_price_list": "_Test Price List", "selling_price_list": "_Test Price List",
"territory": "_Test Territory", "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"): if not frappe.db.exists("POS Setting", "_Test POS Setting"):

View File

@ -1,157 +1,157 @@
[ [
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Sales Taxes and Charges Master", "doctype": "Sales Taxes and Charges Master",
"taxes": [ "taxes": [
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 6 "rate": 6
}, },
{ {
"account_head": "_Test Account Service Tax - _TC", "account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 6.36 "rate": 6.36
} }
], ],
"title": "_Test Sales Taxes and Charges Master", "title": "_Test Sales Taxes and Charges Master",
"territories": [ "territories": [
{ {
"doctype": "Applicable Territory", "doctype": "Applicable Territory",
"parentfield": "territories", "parentfield": "territories",
"territory": "All Territories" "territory": "All Territories"
}, },
{ {
"doctype": "Applicable Territory", "doctype": "Applicable Territory",
"parentfield": "territories", "parentfield": "territories",
"territory": "_Test Territory Rest Of The World" "territory": "_Test Territory Rest Of The World"
} }
] ]
}, },
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Sales Taxes and Charges Master", "doctype": "Sales Taxes and Charges Master",
"taxes": [ "taxes": [
{ {
"account_head": "_Test Account Shipping Charges - _TC", "account_head": "_Test Account Shipping Charges - _TC",
"charge_type": "Actual", "charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges", "description": "Shipping Charges",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 100 "tax_amount": 100
}, },
{ {
"account_head": "_Test Account Customs Duty - _TC", "account_head": "_Test Account Customs Duty - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty", "description": "Customs Duty",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 10 "rate": 10
}, },
{ {
"account_head": "_Test Account Excise Duty - _TC", "account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty", "description": "Excise Duty",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12 "rate": 12
}, },
{ {
"account_head": "_Test Account Education Cess - _TC", "account_head": "_Test Account Education Cess - _TC",
"charge_type": "On Previous Row Amount", "charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "Education Cess", "description": "Education Cess",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 2, "rate": 2,
"row_id": 3 "row_id": 3
}, },
{ {
"account_head": "_Test Account S&H Education Cess - _TC", "account_head": "_Test Account S&H Education Cess - _TC",
"charge_type": "On Previous Row Amount", "charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "S&H Education Cess", "description": "S&H Education Cess",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 1, "rate": 1,
"row_id": 3 "row_id": 3
}, },
{ {
"account_head": "_Test Account CST - _TC", "account_head": "_Test Account CST - _TC",
"charge_type": "On Previous Row Total", "charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "CST", "description": "CST",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 2, "rate": 2,
"row_id": 5 "row_id": 5
}, },
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12.5 "rate": 12.5
}, },
{ {
"account_head": "_Test Account Discount - _TC", "account_head": "_Test Account Discount - _TC",
"charge_type": "On Previous Row Total", "charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "Discount", "description": "Discount",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": -10, "rate": -10,
"row_id": 7 "row_id": 7
} }
], ],
"title": "_Test India Tax Master", "title": "_Test India Tax Master",
"territories": [ "territories": [
{ {
"doctype": "Applicable Territory", "doctype": "Applicable Territory",
"parentfield": "territories", "parentfield": "territories",
"territory": "_Test Territory India" "territory": "_Test Territory India"
} }
] ]
}, },
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Sales Taxes and Charges Master", "doctype": "Sales Taxes and Charges Master",
"taxes": [ "taxes": [
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 12 "rate": 12
}, },
{ {
"account_head": "_Test Account Service Tax - _TC", "account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"rate": 4 "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": [ "territories": [
{ {
"doctype": "Applicable Territory", "doctype": "Applicable Territory",
"parentfield": "territories", "parentfield": "territories",
"territory": "_Test Territory Rest Of The World" "territory": "_Test Territory Rest Of The World"
} }
] ]
} }
] ]

View File

@ -226,13 +226,13 @@ class calculate_taxes_and_totals(object):
# set precision in the last item iteration # set precision in the last item iteration
if n == len(self.doc.get("items")) - 1: if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax) self.round_off_totals(tax)
# adjust Discount Amount loss in last tax iteration # adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
and self.doc.discount_amount: and self.doc.discount_amount:
self.adjust_discount_amount_loss(tax) self.adjust_discount_amount_loss(tax)
def get_current_tax_amount(self, item, tax, item_tax_map): def get_current_tax_amount(self, item, tax, item_tax_map):
tax_rate = self._get_tax_rate(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 + tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount +
discount_amount_loss, tax.precision("tax_amount")) discount_amount_loss, tax.precision("tax_amount"))
tax.total = flt(tax.total + discount_amount_loss, tax.precision("total")) tax.total = flt(tax.total + discount_amount_loss, tax.precision("total"))
self._set_in_company_currency(tax, ["total", "tax_amount_after_discount_amount"]) self._set_in_company_currency(tax, ["total", "tax_amount_after_discount_amount"])
def calculate_totals(self): def calculate_totals(self):
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total self.doc.grand_total = flt(self.doc.get("taxes")[-1].total
if self.doc.get("taxes") else self.doc.net_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"]: 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) \ 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 if self.doc.total_taxes_and_charges else self.doc.base_net_total
else: 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"): for tax in self.doc.get("taxes"):
if tax.category in ["Valuation and Total", "Total"]: if tax.category in ["Valuation and Total", "Total"]:
if tax.add_deduct_tax == "Add": 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._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"]) self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
if self.doc.meta.get_field("rounded_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")): for i, item in enumerate(self.doc.get("items")):
distributed_amount = flt(self.doc.discount_amount) * \ distributed_amount = flt(self.doc.discount_amount) * \
item.net_amount / total_for_discount_amount item.net_amount / total_for_discount_amount
item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount")) item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
net_total += item.net_amount net_total += item.net_amount
# discount amount rounding loss adjustment if no taxes # discount amount rounding loss adjustment if no taxes
if (not taxes or self.doc.apply_discount_on == "Net Total") \ if (not taxes or self.doc.apply_discount_on == "Net Total") \
and i == len(self.doc.get("items")) - 1: 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")) 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.precision("net_amount"))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
self._set_in_company_currency(item, ["net_rate", "net_amount"]) self._set_in_company_currency(item, ["net_rate", "net_amount"])
self.discount_amount_applied = True self.discount_amount_applied = True

View File

@ -124,6 +124,9 @@ class Opportunity(TransactionBase):
item_fields = ("item_name", "description", "item_group", "brand") item_fields = ("item_name", "description", "item_group", "brand")
for d in self.items: 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) item = frappe.db.get_value("Item", d.item_code, item_fields, as_dict=True)
for key in item_fields: for key in item_fields:
if not d.get(key): d.set(key, item.get(key)) if not d.get(key): d.set(key, item.get(key))

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe 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.model.naming import make_autoname
from frappe import throw, _, msgprint from frappe import throw, _, msgprint
import frappe.permissions import frappe.permissions
@ -205,6 +205,6 @@ def send_birthday_reminders():
def get_employees_who_are_born_today(): def get_employees_who_are_born_today():
"""Get Employee properties whose birthday is today.""" """Get Employee properties whose birthday is today."""
return frappe.db.sql("""select name, personal_email, company_email, employee_name return frappe.db.sql("""select name, personal_email, company_email, employee_name
from tabEmployee where day(date_of_birth) = day(curdate()) from tabEmployee where day(date_of_birth) = day(%(date)s)
and month(date_of_birth) = month(curdate()) and month(date_of_birth) = month(%(date)s)
and status = 'Active'""", as_dict=True) and status = 'Active'""", {"date": today()}, as_dict=True)

View File

@ -1,99 +1,99 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"creation": "2014-11-27 14:12:07.542534", "creation": "2014-11-27 14:12:07.542534",
"custom": 0, "custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Master",
"fields": [ "fields": [
{ {
"fieldname": "capacity_planning", "fieldname": "capacity_planning",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Capacity Planning", "label": "Capacity Planning",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"description": "Plan time logs outside Workstation Working Hours.", "description": "Plan time logs outside Workstation Working Hours.",
"fieldname": "allow_overtime", "fieldname": "allow_overtime",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Overtime", "label": "Allow Overtime",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"default": "", "default": "",
"fieldname": "allow_production_on_holidays", "fieldname": "allow_production_on_holidays",
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 1, "in_list_view": 1,
"label": "Allow Production on Holidays", "label": "Allow Production on Holidays",
"options": "", "options": "",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"default": "30", "default": "30",
"description": "Try planning operations for X days in advance.", "description": "Try planning operations for X days in advance.",
"fieldname": "capacity_planning_for_days", "fieldname": "capacity_planning_for_days",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Capacity Planning For (Days)", "label": "Capacity Planning For (Days)",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"description": "Default 10 mins", "description": "Default 10 mins",
"fieldname": "mins_between_operations", "fieldname": "mins_between_operations",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Time Between Operations (in mins)", "label": "Time Between Operations (in mins)",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
} }
], ],
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "icon-wrench", "icon": "icon-wrench",
"in_create": 0, "in_create": 0,
"in_dialog": 0, "in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"modified": "2015-02-23 23:44:45.917027", "modified": "2015-03-05 23:44:45.917027",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing Settings", "name": "Manufacturing Settings",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 0, "delete": 0,
"email": 0, "email": 0,
"export": 0, "export": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 0, "print": 0,
"read": 1, "read": 1,
"report": 0, "report": 0,
"role": "Manufacturing Manager", "role": "Manufacturing Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
} }
], ],
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"
} }

View File

@ -200,7 +200,7 @@ $.extend(cur_frm.cscript, {
}, },
show_time_logs: function(doc, doctype, name) { show_time_logs: function(doc, doctype, name) {
frappe.route_options = {"operation_id": name}; frappe.route_options = {"operation": name};
frappe.set_route("List", "Time Log"); frappe.set_route("List", "Time Log");
}, },

View File

@ -1,385 +1,386 @@
{ {
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-01-10 16:34:16", "creation": "2013-01-10 16:34:16",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{ {
"fieldname": "item", "fieldname": "item",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "", "label": "",
"options": "icon-gift", "options": "icon-gift",
"permlevel": 0 "permlevel": 0
}, },
{ {
"default": "PRO-", "default": "PRO-",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"options": "PRO-", "options": "PRO-",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"default": "Draft", "default": "Draft",
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_filter": 1, "in_filter": 1,
"in_list_view": 0, "in_list_view": 0,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nStopped\nIn Process\nCompleted\nCancelled", "options": "\nDraft\nSubmitted\nStopped\nIn Process\nCompleted\nCancelled",
"permlevel": 0, "permlevel": 0,
"read_only": 1, "read_only": 1,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
{ {
"fieldname": "production_item", "fieldname": "production_item",
"fieldtype": "Link", "fieldtype": "Link",
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Item To Manufacture", "label": "Item To Manufacture",
"oldfieldname": "production_item", "oldfieldname": "production_item",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item", "options": "Item",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "", "depends_on": "",
"description": "", "description": "",
"fieldname": "bom_no", "fieldname": "bom_no",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 0, "in_list_view": 0,
"label": "BOM No", "label": "BOM No",
"oldfieldname": "bom_no", "oldfieldname": "bom_no",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "BOM", "options": "BOM",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"default": "1", "default": "1",
"description": "Plan material for sub-assemblies", "description": "Plan material for sub-assemblies",
"fieldname": "use_multi_level_bom", "fieldname": "use_multi_level_bom",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Multi-Level BOM", "label": "Use Multi-Level BOM",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"width": "50%" "width": "50%"
}, },
{ {
"depends_on": "", "depends_on": "",
"fieldname": "qty", "fieldname": "qty",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 0, "in_list_view": 0,
"label": "Qty To Manufacture", "label": "Qty To Manufacture",
"oldfieldname": "qty", "oldfieldname": "qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"description": "", "description": "",
"fieldname": "material_transferred_for_qty", "fieldname": "material_transferred_for_qty",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Material Transferred for Qty", "label": "Material Transferred for Qty",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.docstatus==1", "depends_on": "eval:doc.docstatus==1",
"description": "", "description": "",
"fieldname": "produced_qty", "fieldname": "produced_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Manufactured Qty", "label": "Manufactured Qty",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "produced_qty", "oldfieldname": "produced_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "warehouses", "fieldname": "warehouses",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Warehouses", "label": "Warehouses",
"options": "icon-building", "options": "icon-building",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "wip_warehouse", "fieldname": "wip_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Work-in-Progress Warehouse", "label": "Work-in-Progress Warehouse",
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
"reqd": 0 "reqd": 0
}, },
{ {
"fieldname": "column_break_12", "fieldname": "column_break_12",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "permlevel": 0
}, },
{ {
"depends_on": "", "depends_on": "",
"description": "", "description": "",
"fieldname": "fg_warehouse", "fieldname": "fg_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 0, "in_list_view": 0,
"label": "Target Warehouse", "label": "Target Warehouse",
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 0 "reqd": 0
}, },
{ {
"fieldname": "time", "fieldname": "time",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Time", "label": "Time",
"options": "icon-time", "options": "icon-time",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"depends_on": "", "depends_on": "",
"fieldname": "expected_delivery_date", "fieldname": "expected_delivery_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Expected Delivery Date", "label": "Expected Delivery Date",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "planned_start_date", "default": "now",
"fieldtype": "Datetime", "fieldname": "planned_start_date",
"label": "Planned Start Date", "fieldtype": "Datetime",
"permlevel": 0, "label": "Planned Start Date",
"permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "planned_end_date", "fieldname": "planned_end_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Planned End Date", "label": "Planned End Date",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "column_break_13", "fieldname": "column_break_13",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "actual_start_date", "fieldname": "actual_start_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Actual Start Date", "label": "Actual Start Date",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "actual_end_date", "fieldname": "actual_end_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Actual End Date", "label": "Actual End Date",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "", "depends_on": "",
"fieldname": "operations_section", "fieldname": "operations_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Operations", "label": "Operations",
"options": "icon-wrench", "options": "icon-wrench",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "operations", "fieldname": "operations",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Operations", "label": "Operations",
"options": "Production Order Operation", "options": "Production Order Operation",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "section_break_22", "fieldname": "section_break_22",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Operation Cost", "label": "Operation Cost",
"options": "", "options": "",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "planned_operating_cost", "fieldname": "planned_operating_cost",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Planned Operating Cost", "label": "Planned Operating Cost",
"no_copy": 0, "no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "actual_operating_cost", "fieldname": "actual_operating_cost",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Actual Operating Cost", "label": "Actual Operating Cost",
"no_copy": 1, "no_copy": 1,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "additional_operating_cost", "fieldname": "additional_operating_cost",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Additional Operating Cost", "label": "Additional Operating Cost",
"no_copy": 1, "no_copy": 1,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "column_break_24", "fieldname": "column_break_24",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "total_operating_cost", "fieldname": "total_operating_cost",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Total Operating Cost", "label": "Total Operating Cost",
"no_copy": 1, "no_copy": 1,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "More Info", "label": "More Info",
"options": "icon-file-text", "options": "icon-file-text",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "description", "fieldname": "description",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Item Description", "label": "Item Description",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "", "depends_on": "",
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Stock UOM", "label": "Stock UOM",
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "UOM", "options": "UOM",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "column_break2", "fieldname": "column_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"width": "50%" "width": "50%"
}, },
{ {
"fieldname": "project_name", "fieldname": "project_name",
"fieldtype": "Link", "fieldtype": "Link",
"in_filter": 1, "in_filter": 1,
"label": "Project Name", "label": "Project Name",
"oldfieldname": "project_name", "oldfieldname": "project_name",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Project", "options": "Project",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"description": "Manufacture against Sales Order", "description": "Manufacture against Sales Order",
"fieldname": "sales_order", "fieldname": "sales_order",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Sales Order", "label": "Sales Order",
"options": "Sales Order", "options": "Sales Order",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"oldfieldname": "company", "oldfieldname": "company",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Company", "options": "Company",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Amended From", "label": "Amended From",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "amended_from", "oldfieldname": "amended_from",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Production Order", "options": "Production Order",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
} }
], ],
"icon": "icon-cogs", "icon": "icon-cogs",
"idx": 1, "idx": 1,
"in_create": 0, "in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"modified": "2015-02-26 04:03:28.164713", "modified": "2015-03-05 13:03:28.164713",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order", "name": "Production Order",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Manufacturing User", "role": "Manufacturing User",
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{ {
"apply_user_permissions": 1, "apply_user_permissions": 1,
"permlevel": 0, "permlevel": 0,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Material User" "role": "Material User"
} }
], ],
"title_field": "production_item" "title_field": "production_item"
} }

View File

@ -4,12 +4,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json 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 import _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from dateutil.parser import parse
class OverProductionError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
@ -64,7 +63,7 @@ class ProductionOrder(Document):
def calculate_operating_cost(self): def calculate_operating_cost(self):
self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
for d in self.get("operations"): 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.planned_operating_cost += flt(d.planned_operating_cost)
self.actual_operating_cost += flt(d.actual_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): def check_operation_fits_in_working_hours(self, d):
"""Raises expection if operation is longer than working hours in the given workstation.""" """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) 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)
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)
def update_operation_status(self): def update_operation_status(self):
for d in self.get("operations"): 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.production_order = name
time_log.project = project time_log.project = project
time_log.operation_id = operation_id time_log.operation_id = operation_id
time_log.operation= operation time_log.operation = operation
time_log.workstation= workstation time_log.workstation= workstation
time_log.activity_type= "Manufacturing" time_log.activity_type= "Manufacturing"
time_log.completed_qty = flt(qty) time_log.completed_qty = flt(qty)
if from_time and to_time : if from_time and to_time :
time_log.calculate_total_hours() time_log.calculate_total_hours()
return time_log return time_log

View File

@ -5,10 +5,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.utils import flt, get_datetime
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory 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.manufacturing.doctype.production_order.production_order import make_stock_entry
from erpnext.stock.doctype.stock_entry import test_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): class TestProductionOrder(unittest.TestCase):
def check_planned_qty(self): def check_planned_qty(self):
@ -27,7 +28,7 @@ class TestProductionOrder(unittest.TestCase):
target="Stores - _TC", qty=100, incoming_rate=100) target="Stores - _TC", qty=100, incoming_rate=100)
# from stores to wip # 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"): for d in s.get("items"):
d.s_warehouse = "Stores - _TC" d.s_warehouse = "Stores - _TC"
s.fiscal_year = "_Test Fiscal Year 2013" 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, self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name,
"produced_qty"), 4) "produced_qty"), 4)
planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
self.assertEqual(planned1 - planned0, 6) self.assertEqual(planned1 - planned0, 6)
return pro_doc return pro_doc
@ -66,38 +68,34 @@ class TestProductionOrder(unittest.TestCase):
self.assertRaises(StockOverProductionError, s.submit) self.assertRaises(StockOverProductionError, s.submit)
def test_make_time_log(self): 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({ prod_order = frappe.get_doc({
"doctype": "Production Order", "doctype": "Production Order",
"production_item": "_Test FG Item 2", "production_item": "_Test FG Item 2",
"bom_no": "BOM/_Test FG Item 2/002", "bom_no": "BOM/_Test FG Item 2/002",
"qty": 1, "qty": 1,
"wip_warehouse": "_Test Warehouse - _TC", "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.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.insert()
prod_order.submit()
d = prod_order.operations[0] d = prod_order.operations[0]
from erpnext.manufacturing.doctype.production_order.production_order import make_time_log d.completed_qty = flt(d.completed_qty)
from frappe.utils import cstr
from frappe.utils import time_diff_in_hours
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.completed_qty,
time_log = make_time_log( prod_order.name, cstr(d.idx) + ". " + d.operation, \ operation_id=d.name)
d.planned_start_time, d.planned_end_time, prod_order.qty - d.qty_completed)
self.assertEqual(prod_order.name, time_log.production_order) 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) self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours)
time_log.save() time_log.save()
@ -105,7 +103,6 @@ class TestProductionOrder(unittest.TestCase):
manufacturing_settings = frappe.get_doc({ manufacturing_settings = frappe.get_doc({
"doctype": "Manufacturing Settings", "doctype": "Manufacturing Settings",
"maximum_overtime": 30,
"allow_production_on_holidays": 0 "allow_production_on_holidays": 0
}) })
@ -113,30 +110,30 @@ class TestProductionOrder(unittest.TestCase):
prod_order.load_from_db() prod_order.load_from_db()
self.assertEqual(prod_order.operations[0].status, "Completed") 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(get_datetime(prod_order.operations[0].actual_start_time), get_datetime(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_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_operation_time, 60)
self.assertEqual(prod_order.operations[0].actual_operating_cost, 6000) self.assertEqual(prod_order.operations[0].actual_operating_cost, 100)
time_log.cancel() time_log.cancel()
prod_order.load_from_db() prod_order.load_from_db()
self.assertEqual(prod_order.operations[0].status, "Pending") 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(flt(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_operating_cost), 0)
time_log2 = frappe.copy_doc(time_log) time_log2 = frappe.copy_doc(time_log)
time_log2.update({ time_log2.update({
"qty": 10, "completed_qty": 10,
"from_time": "2014-11-26 00:00:00", "from_time": "2014-11-26 00:00:00",
"to_time": "2014-11-26 00:00:00", "to_time": "2014-11-26 00:00:00",
"docstatus": 0 "docstatus": 0
}) })
self.assertRaises(OverProductionError, time_log2.save) self.assertRaises(OverProductionLoggedError, time_log2.save)
test_records = frappe.get_test_records('Production Order') test_records = frappe.get_test_records('Production Order')

View File

@ -4,7 +4,10 @@
"name": "_Test Workstation 1", "name": "_Test Workstation 1",
"workstation_name": "_Test Workstation 1", "workstation_name": "_Test Workstation 1",
"warehouse": "_Test warehouse - _TC", "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", "holiday_list": "_Test Holiday List",
"working_hours": [ "working_hours": [
{ {

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from .workstation import check_if_within_operating_hours, NotInWorkingHoursError, WorkstationHolidayError
test_dependencies = ["Warehouse"] test_dependencies = ["Warehouse"]
test_records = frappe.get_test_records('Workstation') test_records = frappe.get_test_records('Workstation')
@ -11,6 +12,11 @@ test_records = frappe.get_test_records('Workstation')
class TestWorkstation(unittest.TestCase): class TestWorkstation(unittest.TestCase):
def test_validate_timings(self): def test_validate_timings(self):
wks = frappe.get_doc("Workstation", "_Test Workstation 1") check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
self.assertEqual(1,wks.check_workstation_for_operation_time("2013-02-01 05:00:00", "2013-02-02 20: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.assertEqual(None,wks.check_workstation_for_operation_time("2013-02-03 10:00:00", "2013-02-03 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")

View File

@ -4,29 +4,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ 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 frappe.model.document import Document
from dateutil.parser import parse
class WorkstationHolidayError(frappe.ValidationError): pass class WorkstationHolidayError(frappe.ValidationError): pass
class NotInWorkingHoursError(frappe.ValidationError): pass class NotInWorkingHoursError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
class Workstation(Document): class Workstation(Document):
def update_bom_operation(self): def validate(self):
bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation` self.hour_rate = (flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) +
where workstation = %s""", self.name) flt(self.hour_rate_consumable) + flt(self.hour_rate_rent))
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 on_update(self): def on_update(self):
self.validate_overlap_for_operation_timings() 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() self.update_bom_operation()
def validate_overlap_for_operation_timings(self): def validate_overlap_for_operation_timings(self):
@ -43,32 +35,35 @@ class Workstation(Document):
if existing: if existing:
frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) 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() @frappe.whitelist()
def get_default_holiday_list(): def get_default_holiday_list():
return frappe.db.get_value("Company", frappe.defaults.get_user_default("company"), "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): def check_if_within_operating_hours(workstation, operation, 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)
if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")): if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")):
check_workstation_for_holiday(workstation, from_datetime, to_datetime) check_workstation_for_holiday(workstation, from_datetime, to_datetime)
def is_within_operating_hours(workstation, from_datetime, to_datetime): if not cint(frappe.db.get_value("Manufacturing Settings", None, "allow_overtime")):
start_time = get_datetime(from_datetime).time() is_within_operating_hours(workstation, operation, from_datetime, to_datetime)
end_time = get_datetime(to_datetime).time()
working_hours = frappe.db.sql_list("""select idx from `tabWorkstation Working Hour` def is_within_operating_hours(workstation, operation, from_datetime, to_datetime):
where parent = %s operation_length = time_diff_in_seconds(to_datetime, from_datetime)
and ( workstation = frappe.get_doc("Workstation", workstation)
(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))
if not working_hours: for working_hour in workstation.working_hours:
frappe.throw(_("Time Log timings outside workstation operating hours"), NotInWorkingHoursError) 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): def check_workstation_for_holiday(workstation, from_datetime, to_datetime):
holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list")

View File

@ -1,129 +1,129 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"creation": "2014-12-24 14:46:40.678236", "creation": "2014-12-24 14:46:40.678236",
"custom": 0, "custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "", "document_type": "",
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"fieldname": "start_time", "fieldname": "start_time",
"fieldtype": "Time", "fieldtype": "Time",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Start Time", "label": "Start Time",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"fieldname": "column_break_2", "fieldname": "column_break_2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"fieldname": "end_time", "fieldname": "end_time",
"fieldtype": "Time", "fieldtype": "Time",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "End Time", "label": "End Time",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"fieldname": "section_break_2", "fieldname": "section_break_2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"default": "1", "default": "1",
"fieldname": "enabled", "fieldname": "enabled",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Enabled", "label": "Enabled",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
], ],
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0, "in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-02-11 14:55:55.650726", "modified": "2015-03-05 14:55:55.650726",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Workstation Working Hour", "name": "Workstation Working Hour",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"
} }

View File

@ -4,4 +4,5 @@ from __future__ import unicode_literals
import frappe import frappe
test_records = frappe.get_test_records('Project') test_records = frappe.get_test_records('Project')
test_ignore = ["Task"]

View File

@ -5,5 +5,5 @@ from __future__ import unicode_literals
import frappe import frappe
test_records = frappe.get_test_records('Task') test_records = frappe.get_test_records('Task')
test_dependencies = ["Project"]
test_ignore = ["Customer"] test_ignore = ["Customer"]

View File

@ -1,10 +1,12 @@
[ [
{ {
"activity_type": "_Test Activity Type", "activity_type": "_Test Activity Type",
"docstatus": 1, "docstatus": 1,
"doctype": "Time Log", "doctype": "Time Log",
"from_time": "2013-01-01 10:00:00.000000", "from_time": "2013-01-01 10:00:00.000000",
"note": "_Test Note", "note": "_Test Note",
"to_time": "2013-01-01 11:00:00.000000" "to_time": "2013-01-01 11:00:00.000000",
"time_log_for": "Project",
"project": "_Test Project"
} }
] ]

View File

@ -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.projects.doctype.time_log.time_log import NotSubmittedError
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError 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 * from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
class TestTimeLog(unittest.TestCase): class TestTimeLog(unittest.TestCase):
def test_duplication(self): def test_duplication(self):
frappe.db.sql("delete from `tabTime Log`") 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])) tl1 = frappe.get_doc(frappe.copy_doc(test_records[0]))
self.assertRaises(OverlapError, ts.insert) 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`") frappe.db.sql("delete from `tabTime Log`")
@ -42,7 +47,7 @@ class TestTimeLog(unittest.TestCase):
def test_time_log_on_holiday(self): def test_time_log_on_holiday(self):
prod_order = make_prod_order(self) prod_order = make_prod_order(self)
prod_order.set_production_order_operations()
prod_order.save() prod_order.save()
prod_order.submit() prod_order.submit()
@ -50,7 +55,10 @@ class TestTimeLog(unittest.TestCase):
"doctype": "Time Log", "doctype": "Time Log",
"time_log_for": "Manufacturing", "time_log_for": "Manufacturing",
"production_order": prod_order.name, "production_order": prod_order.name,
"operation": prod_order.operations[0].operation,
"operation_id": prod_order.operations[0].name,
"qty": 1, "qty": 1,
"activity_type": "_Test Activity Type",
"from_time": "2013-02-01 10:00:00", "from_time": "2013-02-01 10:00:00",
"to_time": "2013-02-01 20:00:00", "to_time": "2013-02-01 20:00:00",
"workstation": "_Test Workstation 1" "workstation": "_Test Workstation 1"
@ -61,9 +69,9 @@ class TestTimeLog(unittest.TestCase):
"from_time": "2013-02-02 09:00:00", "from_time": "2013-02-02 09:00:00",
"to_time": "2013-02-02 20: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.save()
time_log.submit() time_log.submit()
time_log.cancel() time_log.cancel()
@ -84,7 +92,7 @@ def make_prod_order(self):
"bom_no": "BOM/_Test FG Item 2/002", "bom_no": "BOM/_Test FG Item 2/002",
"qty": 1, "qty": 1,
"wip_warehouse": "_Test Warehouse - _TC", "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') test_records = frappe.get_test_records('Time Log')

View File

@ -1,283 +1,283 @@
{ {
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-04-03 16:38:41", "creation": "2013-04-03 16:38:41",
"description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.", "description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Master",
"fields": [ "fields": [
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"options": "TL-", "options": "TL-",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "from_time", "fieldname": "from_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 0, "in_list_view": 0,
"label": "From Time", "label": "From Time",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "to_time", "fieldname": "to_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 0, "in_list_view": 0,
"label": "To Time", "label": "To Time",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "hours", "fieldname": "hours",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Hours", "label": "Hours",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "billable", "fieldname": "billable",
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 0, "in_list_view": 0,
"label": "Billable", "label": "Billable",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "user", "fieldname": "user",
"fieldtype": "Link", "fieldtype": "Link",
"label": "User", "label": "User",
"options": "User", "options": "User",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 0, "in_list_view": 0,
"label": "Status", "label": "Status",
"options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled", "options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled",
"permlevel": 0, "permlevel": 0,
"read_only": 1, "read_only": 1,
"reqd": 0 "reqd": 0
}, },
{ {
"default": "Project", "default": "Project",
"fieldname": "time_log_for", "fieldname": "time_log_for",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Time Log For", "label": "Time Log For",
"options": "\nProject\nManufacturing", "options": "Project\nManufacturing",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1, "read_only": 1,
"reqd": 0 "reqd": 0
}, },
{ {
"depends_on": "", "depends_on": "",
"fieldname": "activity_type", "fieldname": "activity_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 0, "in_list_view": 0,
"label": "Activity Type", "label": "Activity Type",
"options": "Activity Type", "options": "Activity Type",
"permlevel": 0, "permlevel": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:doc.time_log_for != 'Manufacturing'", "depends_on": "eval:doc.time_log_for != 'Manufacturing'",
"fieldname": "task", "fieldname": "task",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Task", "label": "Task",
"options": "Task", "options": "Task",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"depends_on": "eval:doc.time_log_for == 'Manufacturing'", "depends_on": "eval:doc.time_log_for == 'Manufacturing'",
"fieldname": "section_break_11", "fieldname": "section_break_11",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"depends_on": "eval:doc.time_log_for == 'Manufacturing'", "depends_on": "eval:doc.time_log_for == 'Manufacturing'",
"fieldname": "production_order", "fieldname": "production_order",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Production Order", "label": "Production Order",
"options": "Production Order", "options": "Production Order",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:doc.time_log_for == 'Manufacturing'", "depends_on": "eval:doc.time_log_for == 'Manufacturing'",
"fieldname": "operation", "fieldname": "operation",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Operation", "label": "Operation",
"options": "", "options": "",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "operation_id", "fieldname": "operation_id",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Operation ID", "label": "Operation ID",
"options": "", "options": "",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "column_break_14", "fieldname": "column_break_14",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"depends_on": "eval:doc.time_log_for == 'Manufacturing'", "depends_on": "eval:doc.time_log_for == 'Manufacturing'",
"fieldname": "workstation", "fieldname": "workstation",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Workstation", "label": "Workstation",
"options": "Workstation", "options": "Workstation",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:doc.time_log_for == 'Manufacturing'", "depends_on": "eval:doc.time_log_for == 'Manufacturing'",
"description": "Operation completed for how many finished goods?", "description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty", "fieldname": "completed_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Completed Qty", "label": "Completed Qty",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{ {
"fieldname": "section_break_7", "fieldname": "section_break_7",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "note", "fieldname": "note",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"label": "Note", "label": "Note",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "section_break_9", "fieldname": "section_break_9",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"depends_on": "eval:doc.time_log_for", "depends_on": "eval:doc.time_log_for",
"fieldname": "project", "fieldname": "project",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Project", "label": "Project",
"options": "Project", "options": "Project",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"description": "Will be updated when batched.", "description": "Will be updated when batched.",
"fieldname": "time_log_batch", "fieldname": "time_log_batch",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Time Log Batch", "label": "Time Log Batch",
"options": "Time Log Batch", "options": "Time Log Batch",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"description": "Will be updated when billed.", "description": "Will be updated when billed.",
"fieldname": "sales_invoice", "fieldname": "sales_invoice",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Sales Invoice", "label": "Sales Invoice",
"options": "Sales Invoice", "options": "Sales Invoice",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "column_break_16", "fieldname": "column_break_16",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Amended From", "label": "Amended From",
"no_copy": 1, "no_copy": 1,
"options": "Time Log", "options": "Time Log",
"permlevel": 1, "permlevel": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 0 "read_only": 0
}, },
{ {
"fieldname": "title", "fieldname": "title",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Title", "label": "Title",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
} }
], ],
"icon": "icon-time", "icon": "icon-time",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2015-02-26 02:22:10.312376", "modified": "2015-02-26 02:22:10.312376",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Time Log", "name": "Time Log",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Projects User", "role": "Projects User",
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Projects Manager", "role": "Projects Manager",
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"title_field": "title" "title_field": "title"
} }

View File

@ -9,7 +9,7 @@ from dateutil.relativedelta import relativedelta
from dateutil.parser import parse from dateutil.parser import parse
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
class OverProductionError(frappe.ValidationError): pass class OverProductionLoggedError(frappe.ValidationError): pass
class NotSubmittedError(frappe.ValidationError): pass class NotSubmittedError(frappe.ValidationError): pass
from frappe.model.document import Document from frappe.model.document import Document
@ -76,17 +76,22 @@ class TimeLog(Document):
def get_overlap_for(self, fieldname): def get_overlap_for(self, fieldname):
if not self.get(fieldname): if not self.get(fieldname):
return 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 (from_time between %(from_time)s and %(to_time)s) or
(to_time between %s and %s) or (to_time between %(from_time)s and %(to_time)s) or
(%s between from_time and to_time)) (%(from_time)s between from_time and to_time))
and name!=%s and name!=%(name)s
and ifnull(task, "")=%s and ifnull(task, "")=%(task)s
and docstatus < 2""".format(fieldname), 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", "val": self.get(fieldname),
cstr(self.task)), as_dict=True) "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 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**.""" """Checks if **Time Log** is between operating hours of the **Workstation**."""
if self.workstation: if self.workstation:
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours 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): def validate_production_order(self):
"""Throws 'NotSubmittedError' if **production order** is not submitted. """ """Throws 'NotSubmittedError' if **production order** is not submitted. """
@ -194,7 +199,14 @@ class TimeLog(Document):
if not self.operation: if not self.operation:
frappe.throw(_("Operation is Mandatory")) frappe.throw(_("Operation is Mandatory"))
if not self.completed_qty: 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: else:
self.production_order = None self.production_order = None
self.operation = None self.operation = None

View File

@ -34,7 +34,8 @@ def create_time_log():
time_log.update({ time_log.update({
"from_time": "2013-01-02 10:00:00.000000", "from_time": "2013-01-02 10:00:00.000000",
"to_time": "2013-01-02 11: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.insert()
time_log.submit() time_log.submit()

View File

@ -469,7 +469,7 @@ class StockEntry(StockController):
pro_doc = frappe.get_doc("Production Order", self.production_order) pro_doc = frappe.get_doc("Production Order", self.production_order)
_validate_production_order(pro_doc) _validate_production_order(pro_doc)
pro_doc.run_method("update_status") 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") pro_doc.run_method("update_production_order_qty")
self.update_planned_qty(pro_doc) self.update_planned_qty(pro_doc)
@ -543,7 +543,7 @@ class StockEntry(StockController):
def get_items(self): def get_items(self):
if not self.fg_completed_qty or not self.bom_no: if not self.fg_completed_qty or not self.bom_no:
frappe.throw(_("BOM and Manufacturing Quantity are required")) frappe.throw(_("BOM and Manufacturing Quantity are required"))
self.set('items', []) self.set('items', [])
self.validate_production_order() self.validate_production_order()