mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-06 21:59:13 +00:00
@@ -32,6 +32,11 @@ def get_data():
|
|||||||
"name": "Activity Type",
|
"name": "Activity Type",
|
||||||
"description": _("Types of activities for Time Sheets"),
|
"description": _("Types of activities for Time Sheets"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Activity Cost",
|
||||||
|
"description": _("Cost of various activities"),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -133,12 +133,14 @@ cur_frm.cscript.calculate_total = function(doc,cdt,cdn){
|
|||||||
cur_frm.cscript.calculate_total_amount = function(doc,cdt,cdn){
|
cur_frm.cscript.calculate_total_amount = function(doc,cdt,cdn){
|
||||||
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.cscript.claim_amount = function(doc,cdt,cdn){
|
cur_frm.cscript.claim_amount = function(doc,cdt,cdn){
|
||||||
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
||||||
|
|
||||||
var child = locals[cdt][cdn];
|
var child = locals[cdt][cdn];
|
||||||
refresh_field("sanctioned_amount", child.name, child.parentfield);
|
refresh_field("sanctioned_amount", child.name, child.parentfield);
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.cscript.sanctioned_amount = function(doc,cdt,cdn){
|
cur_frm.cscript.sanctioned_amount = function(doc,cdt,cdn){
|
||||||
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
cur_frm.cscript.calculate_total(doc,cdt,cdn);
|
||||||
}
|
}
|
||||||
@@ -148,3 +150,30 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
|||||||
cur_frm.email_doc(frappe.boot.notification_settings.expense_claim_message);
|
cur_frm.email_doc(frappe.boot.notification_settings.expense_claim_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.expense_claim = {
|
||||||
|
set_title :function(frm) {
|
||||||
|
if (!frm.doc.task) {
|
||||||
|
frm.set_value("title", frm.doc.employee_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frm.set_value("title", frm.doc.employee_name + " for "+ frm.doc.task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ui.form.on("Expense Claim", "employee_name", function(frm) {
|
||||||
|
erpnext.expense_claim.set_title(frm);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Expense Claim", "task", function(frm) {
|
||||||
|
erpnext.expense_claim.set_title(frm);
|
||||||
|
});
|
||||||
|
|
||||||
|
cur_frm.fields_dict['task'].get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters:{
|
||||||
|
'project': doc.project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,7 +93,8 @@
|
|||||||
"oldfieldname": "expense_voucher_details",
|
"oldfieldname": "expense_voucher_details",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"options": "Expense Claim Detail",
|
"options": "Expense Claim Detail",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sb1",
|
"fieldname": "sb1",
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Today",
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
@@ -191,6 +193,21 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": ""
|
"precision": ""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "task",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Task",
|
||||||
|
"options": "Task",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Title",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -220,7 +237,7 @@
|
|||||||
"icon": "icon-money",
|
"icon": "icon-money",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2015-03-26 04:41:50.473196",
|
"modified": "2015-04-22 01:51:24.782515",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Expense Claim",
|
"name": "Expense Claim",
|
||||||
@@ -294,5 +311,5 @@
|
|||||||
"search_fields": "approval_status,employee,employee_name",
|
"search_fields": "approval_status,employee,employee_name",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "employee_name"
|
"title_field": "title"
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import get_fullname
|
from frappe.utils import get_fullname, flt
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.hr.utils import set_employee_name
|
from erpnext.hr.utils import set_employee_name
|
||||||
from erpnext.accounts.utils import validate_fiscal_year
|
from erpnext.accounts.utils import validate_fiscal_year
|
||||||
@@ -18,19 +18,44 @@ class ExpenseClaim(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self)
|
validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self)
|
||||||
self.validate_exp_details()
|
self.validate_sanctioned_amount()
|
||||||
self.validate_expense_approver()
|
self.validate_expense_approver()
|
||||||
|
self.validate_task()
|
||||||
|
self.calculate_total_amount()
|
||||||
set_employee_name(self)
|
set_employee_name(self)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.approval_status=="Draft":
|
if self.approval_status=="Draft":
|
||||||
frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'"""))
|
frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'"""))
|
||||||
|
if self.task:
|
||||||
|
self.update_task()
|
||||||
|
|
||||||
def validate_exp_details(self):
|
def on_cancel(self):
|
||||||
if not self.get('expenses'):
|
if self.task:
|
||||||
frappe.throw(_("Please add expense voucher details"))
|
self.update_task()
|
||||||
|
|
||||||
|
def calculate_total_amount(self):
|
||||||
|
self.total_claimed_amount = 0
|
||||||
|
self.total_sanctioned_amount = 0
|
||||||
|
for d in self.get('expenses'):
|
||||||
|
self.total_claimed_amount += flt(d.claim_amount)
|
||||||
|
self.total_sanctioned_amount += flt(d.sanctioned_amount)
|
||||||
|
|
||||||
def validate_expense_approver(self):
|
def validate_expense_approver(self):
|
||||||
if self.exp_approver and "Expense Approver" not in frappe.get_roles(self.exp_approver):
|
if self.exp_approver and "Expense Approver" not in frappe.get_roles(self.exp_approver):
|
||||||
frappe.throw(_("{0} ({1}) must have role 'Expense Approver'")\
|
frappe.throw(_("{0} ({1}) must have role 'Expense Approver'")\
|
||||||
.format(get_fullname(self.exp_approver), self.exp_approver), InvalidExpenseApproverError)
|
.format(get_fullname(self.exp_approver), self.exp_approver), InvalidExpenseApproverError)
|
||||||
|
|
||||||
|
def update_task(self):
|
||||||
|
task = frappe.get_doc("Task", self.task)
|
||||||
|
task.update_total_expense_claim()
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
def validate_task(self):
|
||||||
|
if self.project and not self.task:
|
||||||
|
frappe.throw(_("Task is mandatory if Expense Claim is against a Project"))
|
||||||
|
|
||||||
|
def validate_sanctioned_amount(self):
|
||||||
|
for d in self.get('expenses'):
|
||||||
|
if flt(d.sanctioned_amount) > flt(d.claim_amount):
|
||||||
|
frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx))
|
||||||
@@ -8,4 +8,47 @@ import unittest
|
|||||||
test_records = frappe.get_test_records('Expense Claim')
|
test_records = frappe.get_test_records('Expense Claim')
|
||||||
|
|
||||||
class TestExpenseClaim(unittest.TestCase):
|
class TestExpenseClaim(unittest.TestCase):
|
||||||
pass
|
def test_total_expense_claim_for_project(self):
|
||||||
|
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
|
||||||
|
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
||||||
|
|
||||||
|
frappe.get_doc({
|
||||||
|
"project_name": "_Test Project 1",
|
||||||
|
"doctype": "Project",
|
||||||
|
"tasks" :
|
||||||
|
[{ "title": "_Test Project Task 1", "status": "Open" }]
|
||||||
|
}).save()
|
||||||
|
|
||||||
|
task_name = frappe.db.get_value("Task",{"project": "_Test Project 1"})
|
||||||
|
expense_claim = frappe.get_doc({
|
||||||
|
"doctype": "Expense Claim",
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"approval_status": "Approved",
|
||||||
|
"project": "_Test Project 1",
|
||||||
|
"task": task_name,
|
||||||
|
"expenses":
|
||||||
|
[{ "expense_type": "Food", "claim_amount": 300, "sanctioned_amount": 200 }]
|
||||||
|
})
|
||||||
|
expense_claim.submit()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
||||||
|
|
||||||
|
expense_claim2 = frappe.get_doc({
|
||||||
|
"doctype": "Expense Claim",
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"approval_status": "Approved",
|
||||||
|
"project": "_Test Project 1",
|
||||||
|
"task": task_name,
|
||||||
|
"expenses":
|
||||||
|
[{ "expense_type": "Food", "claim_amount": 600, "sanctioned_amount": 500 }]
|
||||||
|
})
|
||||||
|
expense_claim2.submit()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
|
||||||
|
|
||||||
|
expense_claim2.cancel()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "expense_type",
|
"fieldname": "expense_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -28,6 +34,12 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
@@ -39,6 +51,12 @@
|
|||||||
"print_width": "300px",
|
"print_width": "300px",
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_6",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "claim_amount",
|
"fieldname": "claim_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@@ -52,6 +70,12 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_8",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"fieldname": "sanctioned_amount",
|
"fieldname": "sanctioned_amount",
|
||||||
@@ -69,7 +93,7 @@
|
|||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2014-05-09 02:16:38.529082",
|
"modified": "2015-04-08 06:18:47.539134",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Expense Claim Detail",
|
"name": "Expense Claim Detail",
|
||||||
|
|||||||
@@ -143,3 +143,4 @@ execute:frappe.permissions.reset_perms("Expense Claim Type") #2014-06-19
|
|||||||
erpnext.patches.v5_0.execute_on_doctype_update
|
erpnext.patches.v5_0.execute_on_doctype_update
|
||||||
erpnext.patches.v4_2.fix_recurring_orders
|
erpnext.patches.v4_2.fix_recurring_orders
|
||||||
erpnext.patches.v4_2.delete_gl_entries_for_cancelled_invoices
|
erpnext.patches.v4_2.delete_gl_entries_for_cancelled_invoices
|
||||||
|
erpnext.patches.v5_0.project_costing
|
||||||
|
|||||||
7
erpnext/patches/v5_0/project_costing.py
Normal file
7
erpnext/patches/v5_0/project_costing.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doctype("Project")
|
||||||
|
frappe.db.sql("update `tabProject` set expected_start_date = project_start_date, \
|
||||||
|
expected_end_date = completion_date, actual_end_date = act_completion_date, \
|
||||||
|
estimated_costing = project_value, gross_margin = gross_margin_value")
|
||||||
0
erpnext/projects/doctype/activity_cost/__init__.py
Normal file
0
erpnext/projects/doctype/activity_cost/__init__.py
Normal file
170
erpnext/projects/doctype/activity_cost/activity_cost.json
Normal file
170
erpnext/projects/doctype/activity_cost/activity_cost.json
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "Activity Cost - .#",
|
||||||
|
"creation": "2015-03-23 02:00:21.861546",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Master",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Employee",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Employee",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee_name",
|
||||||
|
"fieldtype": "Read Only",
|
||||||
|
"label": "Employee Name",
|
||||||
|
"options": "employee.employee_name",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"fieldname": "activity_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Activity Type",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Activity Type",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"default": "0",
|
||||||
|
"description": "per hour",
|
||||||
|
"fieldname": "billing_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Billing Rate",
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"default": "0",
|
||||||
|
"description": "per hour",
|
||||||
|
"fieldname": "costing_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Costing Rate",
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "title",
|
||||||
|
"no_copy": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 0,
|
||||||
|
"modified": "2015-04-14 02:08:33.690406",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Projects",
|
||||||
|
"name": "Activity Cost",
|
||||||
|
"name_case": "",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"apply_user_permissions": 0,
|
||||||
|
"cancel": 0,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"import": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Projects User",
|
||||||
|
"set_user_permissions": 0,
|
||||||
|
"share": 1,
|
||||||
|
"submit": 0,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "title"
|
||||||
|
}
|
||||||
24
erpnext/projects/doctype/activity_cost/activity_cost.py
Normal file
24
erpnext/projects/doctype/activity_cost/activity_cost.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class DuplicationError(frappe.ValidationError): pass
|
||||||
|
|
||||||
|
class ActivityCost(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.set_title()
|
||||||
|
self.check_unique()
|
||||||
|
|
||||||
|
def set_title(self):
|
||||||
|
self.title = _("{0} for {1}").format(self.employee_name, self.activity_type)
|
||||||
|
|
||||||
|
def check_unique(self):
|
||||||
|
if frappe.db.sql("""select name from `tabActivity Cost` where employee_name= %s and activity_type= %s and name != %s""",
|
||||||
|
(self.employee_name, self.activity_type, self.name)):
|
||||||
|
frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type - {1}")
|
||||||
|
.format(self.employee, self.activity_type), DuplicationError)
|
||||||
24
erpnext/projects/doctype/activity_cost/test_activity_cost.py
Normal file
24
erpnext/projects/doctype/activity_cost/test_activity_cost.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from erpnext.projects.doctype.activity_cost.activity_cost import DuplicationError
|
||||||
|
|
||||||
|
class TestActivityCost(unittest.TestCase):
|
||||||
|
def test_duplication(self):
|
||||||
|
frappe.db.sql("delete from `tabActivity Cost`")
|
||||||
|
activity_cost1 = frappe.new_doc('Activity Cost')
|
||||||
|
activity_cost1.update({
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"employee_name": "_Test Employee",
|
||||||
|
"activity_type": "_Test Activity Type",
|
||||||
|
"billing_rate": 100,
|
||||||
|
"costing_rate": 50
|
||||||
|
})
|
||||||
|
activity_cost1.insert()
|
||||||
|
activity_cost2 = frappe.copy_doc(activity_cost1)
|
||||||
|
self.assertRaises(DuplicationError, activity_cost2.insert )
|
||||||
1
erpnext/projects/doctype/activity_cost/test_records.json
Normal file
1
erpnext/projects/doctype/activity_cost/test_records.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("Project", {
|
||||||
|
onload: function(frm) {
|
||||||
|
var so = frappe.meta.get_docfield("Project", "sales_order");
|
||||||
|
so.get_route_options_for_new_doc = function(field) {
|
||||||
|
if(frm.is_new()) return;
|
||||||
|
return {
|
||||||
|
"customer": frm.doc.customer,
|
||||||
|
"project_name": frm.doc.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Project Task", "edit_task", function(frm, doctype, name) {
|
frappe.ui.form.on("Project Task", "edit_task", function(frm, doctype, name) {
|
||||||
var doc = frappe.get_doc(doctype, name);
|
var doc = frappe.get_doc(doctype, name);
|
||||||
if(doc.task_id) {
|
if(doc.task_id) {
|
||||||
@@ -14,13 +27,21 @@ frappe.ui.form.on("Project Task", "edit_task", function(frm, doctype, name) {
|
|||||||
cur_frm.cscript.refresh = function(doc) {
|
cur_frm.cscript.refresh = function(doc) {
|
||||||
if(!doc.__islocal) {
|
if(!doc.__islocal) {
|
||||||
cur_frm.add_custom_button(__("Gantt Chart"), function() {
|
cur_frm.add_custom_button(__("Gantt Chart"), function() {
|
||||||
frappe.route_options = {"project": doc.name, "start": doc.project_start_date, "end": doc.completion_date};
|
frappe.route_options = {"project": doc.name, "start": doc.expected_start_date, "end": doc.expected_end_date};
|
||||||
frappe.set_route("Gantt", "Task");
|
frappe.set_route("Gantt", "Task");
|
||||||
}, "icon-tasks", true);
|
}, "icon-tasks", true);
|
||||||
cur_frm.add_custom_button(__("Tasks"), function() {
|
cur_frm.add_custom_button(__("Tasks"), function() {
|
||||||
frappe.route_options = {"project": doc.name}
|
frappe.route_options = {"project": doc.name}
|
||||||
frappe.set_route("List", "Task");
|
frappe.set_route("List", "Task");
|
||||||
}, "icon-list", true);
|
}, "icon-list", true);
|
||||||
|
cur_frm.add_custom_button(__("Time Logs"), function() {
|
||||||
|
frappe.route_options = {"project": doc.name}
|
||||||
|
frappe.set_route("List", "Time Log");
|
||||||
|
}, "icon-list", true);
|
||||||
|
cur_frm.add_custom_button(__("Expense Claims"), function() {
|
||||||
|
frappe.route_options = {"project": doc.name}
|
||||||
|
frappe.set_route("List", "Expense Claim");
|
||||||
|
}, "icon-list", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,3 +50,11 @@ cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
|
|||||||
query: "erpnext.controllers.queries.customer_query"
|
query: "erpnext.controllers.queries.customer_query"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cur_frm.fields_dict['sales_order'].get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters:{
|
||||||
|
'project_name': doc.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,19 +7,6 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Master",
|
"document_type": "Master",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"fieldname": "overview",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Overview",
|
|
||||||
"options": "icon-file",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "cb_project_status",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"label": "Status",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "",
|
"description": "",
|
||||||
"fieldname": "project_name",
|
"fieldname": "project_name",
|
||||||
@@ -45,6 +32,23 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Project Type",
|
||||||
|
"no_copy": 0,
|
||||||
|
"oldfieldname": "project_type",
|
||||||
|
"oldfieldtype": "Data",
|
||||||
|
"options": "Internal\nExternal\nOther",
|
||||||
|
"permlevel": 0,
|
||||||
|
"search_index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "is_active",
|
"fieldname": "is_active",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -69,16 +73,16 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cb_project_dates",
|
"fieldname": "section_break_12",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Dates",
|
"permlevel": 0,
|
||||||
"permlevel": 0
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "project_start_date",
|
"fieldname": "expected_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"label": "Project Start Date",
|
"label": "Expected Start Date",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "project_start_date",
|
"oldfieldname": "project_start_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
@@ -86,9 +90,15 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "completion_date",
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Completion Date",
|
"label": "Expected End Date",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "completion_date",
|
"oldfieldname": "completion_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
@@ -96,25 +106,47 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "act_completion_date",
|
"fieldname": "customer_details",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Section Break",
|
||||||
"label": "Actual Completion Date",
|
"label": "",
|
||||||
"no_copy": 0,
|
"oldfieldtype": "Section Break",
|
||||||
"oldfieldname": "act_completion_date",
|
"options": "icon-user",
|
||||||
"oldfieldtype": "Date",
|
"permlevel": 0
|
||||||
"permlevel": 0,
|
|
||||||
"search_index": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "project_type",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Link",
|
||||||
"label": "Project Type",
|
"in_filter": 1,
|
||||||
|
"label": "Customer",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "project_type",
|
"oldfieldname": "customer",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Link",
|
||||||
"options": "Internal\nExternal\nOther",
|
"options": "Customer",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"search_index": 0
|
"print_hide": 1,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sales Order",
|
||||||
|
"options": "Sales Order",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sb_milestones",
|
"fieldname": "sb_milestones",
|
||||||
@@ -130,7 +162,8 @@
|
|||||||
"label": "Tasks",
|
"label": "Tasks",
|
||||||
"options": "Project Task",
|
"options": "Project Task",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": ""
|
"precision": "",
|
||||||
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "percent_complete",
|
"fieldname": "percent_complete",
|
||||||
@@ -159,25 +192,55 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "section_break_18",
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "project_details",
|
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Project Costing",
|
"permlevel": 0,
|
||||||
"oldfieldtype": "Section Break",
|
"precision": ""
|
||||||
"options": "icon-money",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "project_value",
|
"fieldname": "actual_start_date",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Actual Start Date",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_time",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Actual Time (in Hours)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_20",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Actual End Date",
|
||||||
|
"no_copy": 0,
|
||||||
|
"oldfieldname": "act_completion_date",
|
||||||
|
"oldfieldtype": "Date",
|
||||||
|
"permlevel": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_26",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "estimated_costing",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Project Value",
|
"label": "Estimated Costing",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "project_value",
|
"oldfieldname": "project_value",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
@@ -187,15 +250,10 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "est_material_cost",
|
"fieldname": "column_break_22",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Column Break",
|
||||||
"label": "Estimated Material Cost",
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "est_material_cost",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"search_index": 0
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
@@ -205,64 +263,91 @@
|
|||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break0",
|
"fieldname": "project_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "",
|
||||||
|
"oldfieldtype": "Section Break",
|
||||||
|
"options": "icon-money",
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "total_costing_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Costing Amount (via Time Logs)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "total_expense_claim",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Expense Claim (via Expense Claims)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_28",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"label": "Margin",
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "total_billing_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Billing Amount (via Time Logs)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "margin",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "",
|
||||||
"oldfieldtype": "Column Break",
|
"oldfieldtype": "Column Break",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "gross_margin_value",
|
"fieldname": "gross_margin",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Gross Margin Value",
|
"label": "Gross Margin",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "gross_margin_value",
|
"oldfieldname": "gross_margin_value",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
|
"read_only": 1,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_37",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "per_gross_margin",
|
"fieldname": "per_gross_margin",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Percent",
|
||||||
"label": "Gross Margin %",
|
"label": "Gross Margin %",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"oldfieldname": "per_gross_margin",
|
"oldfieldname": "per_gross_margin",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
|
"read_only": 1,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "customer_details",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Customer Details",
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"options": "icon-user",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "customer",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_filter": 1,
|
|
||||||
"label": "Customer",
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "customer",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Customer",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-puzzle-piece",
|
"icon": "icon-puzzle-piece",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"max_attachments": 4,
|
"max_attachments": 4,
|
||||||
"modified": "2015-02-22 11:17:49.051755",
|
"modified": "2015-04-14 07:37:56.810833",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Project",
|
"name": "Project",
|
||||||
|
|||||||
@@ -25,23 +25,19 @@ class Project(Document):
|
|||||||
"task_id": task.name
|
"task_id": task.name
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_gross_profit(self):
|
|
||||||
pft, per_pft =0, 0
|
|
||||||
pft = flt(self.project_value) - flt(self.est_material_cost)
|
|
||||||
#if pft > 0:
|
|
||||||
per_pft = (flt(pft) / flt(self.project_value)) * 100
|
|
||||||
ret = {'gross_margin_value': pft, 'per_gross_margin': per_pft}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.project_start_date and self.completion_date:
|
self.validate_dates()
|
||||||
if getdate(self.completion_date) < getdate(self.project_start_date):
|
|
||||||
frappe.throw(_("Expected Completion Date can not be less than Project Start Date"))
|
|
||||||
|
|
||||||
self.sync_tasks()
|
self.sync_tasks()
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
|
if self.expected_start_date and self.expected_end_date:
|
||||||
|
if getdate(self.expected_end_date) < getdate(self.expected_start_date):
|
||||||
|
frappe.throw(_("Expected End Date can not be less than Expected Start Date"))
|
||||||
|
|
||||||
def sync_tasks(self):
|
def sync_tasks(self):
|
||||||
"""sync tasks and remove table"""
|
"""sync tasks and remove table"""
|
||||||
|
if self.flags.dont_sync_tasks: return
|
||||||
|
|
||||||
task_names = []
|
task_names = []
|
||||||
for t in self.tasks:
|
for t in self.tasks:
|
||||||
if t.task_id:
|
if t.task_id:
|
||||||
@@ -78,6 +74,21 @@ class Project(Document):
|
|||||||
frappe.db.set_value("Project", self.name, "percent_complete",
|
frappe.db.set_value("Project", self.name, "percent_complete",
|
||||||
int(float(completed) / total * 100))
|
int(float(completed) / total * 100))
|
||||||
|
|
||||||
|
def update_costing(self):
|
||||||
|
total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount,
|
||||||
|
sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim,
|
||||||
|
min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time
|
||||||
|
from `tabTask` where project = %s""", self.name, as_dict=1)[0]
|
||||||
|
|
||||||
|
self.total_costing_amount = total_cost.costing_amount
|
||||||
|
self.total_billing_amount = total_cost.billing_amount
|
||||||
|
self.total_expense_claim = total_cost.expense_claim
|
||||||
|
self.actual_start_date = total_cost.start_date
|
||||||
|
self.actual_end_date = total_cost.end_date
|
||||||
|
self.actual_time = total_cost.time
|
||||||
|
self.gross_margin = flt(total_cost.billing_amount) - flt(total_cost.costing_amount)
|
||||||
|
if self.total_billing_amount:
|
||||||
|
self.per_gross_margin = (self.gross_margin / flt(self.total_billing_amount)) *100
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_cost_center_name(project_name):
|
def get_cost_center_name(project_name):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
frappe.listview_settings['Project'] = {
|
frappe.listview_settings['Project'] = {
|
||||||
add_fields: ["status", "priority", "is_active", "percent_complete", "completion_date"],
|
add_fields: ["status", "priority", "is_active", "percent_complete", "expected_end_date"],
|
||||||
filters:[["status","=", "Open"]],
|
filters:[["status","=", "Open"]],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if(doc.status=="Open" && doc.percent_complete) {
|
if(doc.status=="Open" && doc.percent_complete) {
|
||||||
|
|||||||
@@ -5,4 +5,3 @@ 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"]
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"project_name": "_Test Project",
|
"project_name": "_Test Project",
|
||||||
"status": "Open"
|
"status": "Open",
|
||||||
},
|
"tasks":[
|
||||||
{
|
{
|
||||||
"project_name": "_Test Project 1",
|
"title": "_Test Task",
|
||||||
"status": "Open"
|
"status": "Open"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 0,
|
"no_copy": 1,
|
||||||
"options": "Open\nWorking\nPending Review\nClosed\nCancelled",
|
"options": "Open\nWorking\nPending Review\nClosed\nCancelled",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "edit_task",
|
"fieldname": "edit_task",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Edit Task",
|
"label": "View Task",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": ""
|
"precision": ""
|
||||||
},
|
},
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2015-02-23 01:55:18.865117",
|
"modified": "2015-04-13 04:56:18.766659",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Project Task",
|
"name": "Project Task",
|
||||||
|
|||||||
@@ -25,6 +25,19 @@ erpnext.projects.Task = frappe.ui.form.Controller.extend({
|
|||||||
this.frm.doc.project && frappe.model.remove_from_locals("Project",
|
this.frm.doc.project && frappe.model.remove_from_locals("Project",
|
||||||
this.frm.doc.project);
|
this.frm.doc.project);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refresh: function(doc) {
|
||||||
|
if(!doc.__islocal) {
|
||||||
|
cur_frm.add_custom_button(__("Time Logs"), function() {
|
||||||
|
frappe.route_options = {"project": doc.project, "task": doc.name}
|
||||||
|
frappe.set_route("List", "Time Log");
|
||||||
|
}, "icon-list", true);
|
||||||
|
cur_frm.add_custom_button(__("Expense Claims"), function() {
|
||||||
|
frappe.route_options = {"project": doc.project, "task": doc.name}
|
||||||
|
frappe.set_route("List", "Expense Claim");
|
||||||
|
}, "icon-list", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,6 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Master",
|
"document_type": "Master",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"fieldname": "task_details",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "",
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_width": "50%",
|
|
||||||
"search_index": 0,
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -28,24 +18,14 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "exp_start_date",
|
"fieldname": "project",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Link",
|
||||||
"label": "Expected Start Date",
|
"in_list_view": 1,
|
||||||
"oldfieldname": "exp_start_date",
|
"label": "Project",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldname": "project",
|
||||||
"permlevel": 0,
|
"oldfieldtype": "Link",
|
||||||
"reqd": 0
|
"options": "Project",
|
||||||
},
|
"permlevel": 0
|
||||||
{
|
|
||||||
"fieldname": "exp_end_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_filter": 1,
|
|
||||||
"label": "Expected End Date",
|
|
||||||
"oldfieldname": "exp_end_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"permlevel": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break0",
|
"fieldname": "column_break0",
|
||||||
@@ -66,16 +46,6 @@
|
|||||||
"options": "Open\nWorking\nPending Review\nClosed\nCancelled",
|
"options": "Open\nWorking\nPending Review\nClosed\nCancelled",
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "project",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Project",
|
|
||||||
"oldfieldname": "project",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Project",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "priority",
|
"fieldname": "priority",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -110,41 +80,52 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "time_and_budget",
|
"fieldname": "time_and_budget",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Time and Budget",
|
"label": "",
|
||||||
"oldfieldtype": "Section Break",
|
"oldfieldtype": "Section Break",
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "expected",
|
"fieldname": "exp_start_date",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Date",
|
||||||
"label": "Expected",
|
"label": "Expected Start Date",
|
||||||
"oldfieldtype": "Column Break",
|
"oldfieldname": "exp_start_date",
|
||||||
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_width": "50%",
|
"reqd": 0
|
||||||
"width": "50%"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "exp_total_hrs",
|
"default": "0",
|
||||||
"fieldtype": "Data",
|
"description": "",
|
||||||
"label": "Total Hours (Expected)",
|
"fieldname": "expected_time",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Expected Time (in hours)",
|
||||||
"oldfieldname": "exp_total_hrs",
|
"oldfieldname": "exp_total_hrs",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "allocated_budget",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Column Break",
|
||||||
"label": "Allocated Budget",
|
"permlevel": 0,
|
||||||
"oldfieldname": "allocated_budget",
|
"precision": ""
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fieldname": "exp_end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_filter": 1,
|
||||||
|
"label": "Expected End Date",
|
||||||
|
"oldfieldname": "exp_end_date",
|
||||||
|
"oldfieldtype": "Date",
|
||||||
|
"permlevel": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
"fieldname": "actual",
|
"fieldname": "actual",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Actual",
|
"label": "",
|
||||||
"oldfieldtype": "Column Break",
|
"oldfieldtype": "Column Break",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_width": "50%",
|
"print_width": "50%",
|
||||||
@@ -153,27 +134,77 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "act_start_date",
|
"fieldname": "act_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Actual Start Date",
|
"label": "Actual Start Date (via Time Logs)",
|
||||||
"oldfieldname": "act_start_date",
|
"oldfieldname": "act_start_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "",
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "actual_time",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Actual Time (in hours)",
|
||||||
|
"options": "",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_15",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "act_end_date",
|
"fieldname": "act_end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Actual End Date",
|
"label": "Actual End Date (via Time Logs)",
|
||||||
"oldfieldname": "act_end_date",
|
"oldfieldname": "act_end_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "actual_budget",
|
"fieldname": "section_break_17",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_costing_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Actual Budget",
|
"label": "Total Costing Amount (via Time Logs)",
|
||||||
"oldfieldname": "actual_budget",
|
"oldfieldname": "actual_budget",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_expense_claim",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Expense Claim (via Expense Claim)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_20",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_billing_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Total Billing Amount (via Time Logs)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "more_details",
|
"fieldname": "more_details",
|
||||||
@@ -216,8 +247,9 @@
|
|||||||
],
|
],
|
||||||
"icon": "icon-check",
|
"icon": "icon-check",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"istable": 0,
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2015-02-20 05:09:27.295024",
|
"modified": "2015-04-14 07:56:24.481667",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json
|
import frappe, json
|
||||||
|
|
||||||
from frappe.utils import getdate, today
|
from frappe.utils import getdate
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
@@ -26,28 +26,49 @@ class Task(Document):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_dates()
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
|
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
|
||||||
frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'"))
|
frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'"))
|
||||||
|
|
||||||
if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
|
if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
|
||||||
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
|
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
|
||||||
|
|
||||||
self.update_status()
|
|
||||||
|
|
||||||
def update_status(self):
|
|
||||||
status = frappe.db.get_value("Task", self.name, "status")
|
|
||||||
if self.status=="Working" and status !="Working" and not self.act_start_date:
|
|
||||||
self.act_start_date = today()
|
|
||||||
|
|
||||||
if self.status=="Closed" and status != "Closed" and not self.act_end_date:
|
|
||||||
self.act_end_date = today()
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
|
self.update_percentage()
|
||||||
|
self.update_project()
|
||||||
|
|
||||||
|
def update_percentage(self):
|
||||||
"""update percent complete in project"""
|
"""update percent complete in project"""
|
||||||
if self.project and not self.flags.from_project:
|
if self.project and not self.flags.from_project:
|
||||||
project = frappe.get_doc("Project", self.project)
|
project = frappe.get_doc("Project", self.project)
|
||||||
project.run_method("update_percent_complete")
|
project.run_method("update_percent_complete")
|
||||||
|
|
||||||
|
def update_total_expense_claim(self):
|
||||||
|
self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
|
||||||
|
where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.name))
|
||||||
|
|
||||||
|
def update_time_and_costing(self):
|
||||||
|
tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date,
|
||||||
|
sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
|
||||||
|
sum(hours) as time from `tabTime Log` where project = %s and task = %s and docstatus=1""",
|
||||||
|
(self.project, self.name),as_dict=1)[0]
|
||||||
|
if self.status == "Open":
|
||||||
|
self.status = "Working"
|
||||||
|
self.total_costing_amount= tl.total_costing_amount
|
||||||
|
self.total_billing_amount= tl.total_billing_amount
|
||||||
|
self.actual_time= tl.time
|
||||||
|
self.act_start_date= tl.start_date
|
||||||
|
self.act_end_date= tl.end_date
|
||||||
|
|
||||||
|
def update_project(self):
|
||||||
|
if self.project and frappe.db.exists("Project", self.project):
|
||||||
|
project = frappe.get_doc("Project", self.project)
|
||||||
|
project.flags.dont_sync_tasks = True
|
||||||
|
project.update_costing()
|
||||||
|
project.save()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_events(start, end, filters=None):
|
def get_events(start, end, filters=None):
|
||||||
from frappe.desk.reportview import build_match_conditions
|
from frappe.desk.reportview import build_match_conditions
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"project": "_Test Project",
|
|
||||||
"status": "Open",
|
"status": "Open",
|
||||||
"subject": "_Test Task"
|
"subject": "_Test Task",
|
||||||
|
"name": "task001"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "Open",
|
"status": "Open",
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ 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"]
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"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"
|
||||||
"project": "_Test Project"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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 NotInWorkingHoursError
|
from erpnext.manufacturing.doctype.workstation.workstation import NotInWorkingHoursError
|
||||||
from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
|
|
||||||
from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record
|
from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record
|
||||||
|
|
||||||
|
|
||||||
@@ -86,5 +85,71 @@ class TestTimeLog(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.ValidationError, test_time_log.save)
|
self.assertRaises(frappe.ValidationError, test_time_log.save)
|
||||||
frappe.db.sql("delete from `tabTime Log`")
|
frappe.db.sql("delete from `tabTime Log`")
|
||||||
|
|
||||||
|
def test_total_activity_cost_for_project(self):
|
||||||
|
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
|
||||||
|
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
||||||
|
frappe.db.sql("""delete from `tabActivity Cost` where employee = "_T-Employee-0001" and activity_type = "_Test Activity Type" """)
|
||||||
|
|
||||||
|
activity_cost = frappe.new_doc('Activity Cost')
|
||||||
|
activity_cost.update({
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"employee_name": "_Test Employee",
|
||||||
|
"activity_type": "_Test Activity Type",
|
||||||
|
"billing_rate": 100,
|
||||||
|
"costing_rate": 50
|
||||||
|
})
|
||||||
|
activity_cost.insert()
|
||||||
|
|
||||||
|
frappe.get_doc({
|
||||||
|
"project_name": "_Test Project 1",
|
||||||
|
"doctype": "Project",
|
||||||
|
"tasks" :
|
||||||
|
[{ "title": "_Test Project Task 1", "status": "Open" }]
|
||||||
|
}).save()
|
||||||
|
|
||||||
|
task_name = frappe.db.get_value("Task",{"project": "_Test Project 1"})
|
||||||
|
|
||||||
|
time_log = frappe.get_doc({
|
||||||
|
"activity_type": "_Test Activity Type",
|
||||||
|
"docstatus": 1,
|
||||||
|
"doctype": "Time Log",
|
||||||
|
"from_time": "2013-02-02 09:00:00.000000",
|
||||||
|
"to_time": "2013-02-02 11:00:00.000000",
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"project": "_Test Project 1",
|
||||||
|
"task": task_name,
|
||||||
|
"billable": 1
|
||||||
|
})
|
||||||
|
time_log.save()
|
||||||
|
self.assertEqual(time_log.costing_rate, 50)
|
||||||
|
self.assertEqual(time_log.costing_amount, 100)
|
||||||
|
self.assertEqual(time_log.billing_rate, 100)
|
||||||
|
self.assertEqual(time_log.billing_amount, 200)
|
||||||
|
time_log.submit()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200)
|
||||||
|
|
||||||
|
time_log2 = frappe.get_doc({
|
||||||
|
"activity_type": "_Test Activity Type",
|
||||||
|
"docstatus": 1,
|
||||||
|
"doctype": "Time Log",
|
||||||
|
"from_time": "2013-02-03 09:00:00.000000",
|
||||||
|
"to_time": "2013-02-03 11:00:00.000000",
|
||||||
|
"employee": "_T-Employee-0001",
|
||||||
|
"project": "_Test Project 1",
|
||||||
|
"task": task_name,
|
||||||
|
"billable": 1
|
||||||
|
})
|
||||||
|
time_log2.save()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 400)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 400)
|
||||||
|
|
||||||
|
time_log2.cancel()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200)
|
||||||
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200)
|
||||||
|
|
||||||
test_records = frappe.get_test_records('Time Log')
|
test_records = frappe.get_test_records('Time Log')
|
||||||
test_ignore = ["Time Log Batch", "Sales Invoice"]
|
test_ignore = ["Time Log Batch", "Sales Invoice"]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
frappe.provide("erpnext.projects");
|
frappe.provide("erpnext.projects");
|
||||||
|
|
||||||
frappe.ui.form.on("Time Log", "onload", function(frm) {
|
frappe.ui.form.on("Time Log", "onload", function(frm) {
|
||||||
frm.set_query("task", erpnext.queries.task);
|
|
||||||
if (frm.doc.for_manufacturing) {
|
if (frm.doc.for_manufacturing) {
|
||||||
frappe.ui.form.trigger("Time Log", "production_order");
|
frappe.ui.form.trigger("Time Log", "production_order");
|
||||||
}
|
}
|
||||||
@@ -41,4 +40,60 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) {
|
|||||||
if(frm._setting_hours) return;
|
if(frm._setting_hours) return;
|
||||||
frm.set_value("hours", moment(cur_frm.doc.to_time).diff(moment(cur_frm.doc.from_time),
|
frm.set_value("hours", moment(cur_frm.doc.to_time).diff(moment(cur_frm.doc.from_time),
|
||||||
"hours"));
|
"hours"));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var calculate_cost = function(frm) {
|
||||||
|
frm.set_value("costing_amount", frm.doc.costing_rate * frm.doc.hours);
|
||||||
|
if (frm.doc.billable==1){
|
||||||
|
frm.set_value("billing_amount", frm.doc.billing_rate * frm.doc.hours);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var get_activity_cost = function(frm) {
|
||||||
|
if (frm.doc.employee && frm.doc.activity_type){
|
||||||
|
return frappe.call({
|
||||||
|
method: "erpnext.projects.doctype.time_log.time_log.get_activity_cost",
|
||||||
|
args: {
|
||||||
|
"employee": frm.doc.employee,
|
||||||
|
"activity_type": frm.doc.activity_type
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc && r.message) {
|
||||||
|
frm.set_value("costing_rate", r.message.costing_rate);
|
||||||
|
frm.set_value("billing_rate", r.message.billing_rate);
|
||||||
|
calculate_cost(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ui.form.on("Time Log", "hours", function(frm) {
|
||||||
|
calculate_cost(frm);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Time Log", "activity_type", function(frm) {
|
||||||
|
get_activity_cost(frm);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Time Log", "employee", function(frm) {
|
||||||
|
get_activity_cost(frm);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Time Log", "billable", function(frm) {
|
||||||
|
if (frm.doc.billable==1) {
|
||||||
|
calculate_cost(frm);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frm.set_value("billing_amount", 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cur_frm.fields_dict['task'].get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters:{
|
||||||
|
'project': doc.project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,44 +17,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "from_time",
|
"fieldname": "column_break_2",
|
||||||
"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,
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Hours",
|
|
||||||
"permlevel": 0,
|
|
||||||
"read_only": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "user",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "User",
|
|
||||||
"options": "User",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
@@ -67,20 +33,62 @@
|
|||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "billable",
|
"fieldname": "section_break_4",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_time",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"label": "Billable",
|
"label": "From Time",
|
||||||
|
"permlevel": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Hours",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0
|
"read_only": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "for_manufacturing",
|
"fieldname": "to_time",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Datetime",
|
||||||
"label": "For Manufacturing",
|
"in_list_view": 0,
|
||||||
|
"label": "To Time",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"read_only": 0,
|
||||||
"read_only": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_8",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "",
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project",
|
||||||
|
"permlevel": 0,
|
||||||
|
"read_only": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "",
|
||||||
|
"fieldname": "task",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Task",
|
||||||
|
"options": "Task",
|
||||||
|
"permlevel": 0,
|
||||||
|
"read_only": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.for_manufacturing",
|
"depends_on": "eval:!doc.for_manufacturing",
|
||||||
@@ -94,11 +102,47 @@
|
|||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.for_manufacturing",
|
"fieldname": "section_break_12",
|
||||||
"fieldname": "task",
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "user",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Task",
|
"label": "User",
|
||||||
"options": "Task",
|
"options": "User",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Employee",
|
||||||
|
"options": "Employee",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"read_only": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "for_manufacturing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "For Manufacturing",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billable",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Billable",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0
|
"read_only": 0
|
||||||
},
|
},
|
||||||
@@ -180,20 +224,62 @@
|
|||||||
"read_only": 0
|
"read_only": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_9",
|
"depends_on": "",
|
||||||
|
"fieldname": "section_break_24",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "",
|
"default": "0",
|
||||||
"fieldname": "project",
|
"description": "",
|
||||||
"fieldtype": "Link",
|
"fieldname": "costing_rate",
|
||||||
"in_list_view": 1,
|
"fieldtype": "Currency",
|
||||||
"label": "Project",
|
"label": "Costing Rate (per hour)",
|
||||||
"options": "Project",
|
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "costing_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Costing Amount",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_25",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "billing_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Billing Rate (per hour)",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Will be updated only if Time Log is 'Billable'",
|
||||||
|
"fieldname": "billing_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Billing Amount",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_29",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Will be updated when batched.",
|
"description": "Will be updated when batched.",
|
||||||
@@ -213,12 +299,6 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_16",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"permlevel": 0,
|
|
||||||
"read_only": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -242,7 +322,7 @@
|
|||||||
"icon": "icon-time",
|
"icon": "icon-time",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2015-04-06 02:47:16.187046",
|
"modified": "2015-04-14 09:07:28.468792",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Time Log",
|
"name": "Time Log",
|
||||||
|
|||||||
@@ -24,12 +24,16 @@ class TimeLog(Document):
|
|||||||
self.check_workstation_timings()
|
self.check_workstation_timings()
|
||||||
self.validate_production_order()
|
self.validate_production_order()
|
||||||
self.validate_manufacturing()
|
self.validate_manufacturing()
|
||||||
|
self.validate_task()
|
||||||
|
self.update_cost()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_production_order()
|
self.update_production_order()
|
||||||
|
self.update_task()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_production_order()
|
self.update_production_order()
|
||||||
|
self.update_task()
|
||||||
|
|
||||||
def before_update_after_submit(self):
|
def before_update_after_submit(self):
|
||||||
self.set_status()
|
self.set_status()
|
||||||
@@ -63,6 +67,7 @@ class TimeLog(Document):
|
|||||||
def validate_overlap(self):
|
def validate_overlap(self):
|
||||||
"""Checks if 'Time Log' entries overlap for a user, workstation. """
|
"""Checks if 'Time Log' entries overlap for a user, workstation. """
|
||||||
self.validate_overlap_for("user")
|
self.validate_overlap_for("user")
|
||||||
|
self.validate_overlap_for("employee")
|
||||||
self.validate_overlap_for("workstation")
|
self.validate_overlap_for("workstation")
|
||||||
|
|
||||||
def validate_overlap_for(self, fieldname):
|
def validate_overlap_for(self, fieldname):
|
||||||
@@ -124,7 +129,7 @@ class TimeLog(Document):
|
|||||||
def update_production_order(self):
|
def update_production_order(self):
|
||||||
"""Updates `start_date`, `end_date`, `status` for operation in Production Order."""
|
"""Updates `start_date`, `end_date`, `status` for operation in Production Order."""
|
||||||
|
|
||||||
if self.for_manufacturing and self.production_order:
|
if self.production_order and self.for_manufacturing:
|
||||||
if not self.operation_id:
|
if not self.operation_id:
|
||||||
frappe.throw(_("Operation ID not set"))
|
frappe.throw(_("Operation ID not set"))
|
||||||
|
|
||||||
@@ -206,6 +211,27 @@ class TimeLog(Document):
|
|||||||
self.operation = None
|
self.operation = None
|
||||||
self.quantity = None
|
self.quantity = None
|
||||||
|
|
||||||
|
def update_cost(self):
|
||||||
|
rate = get_activity_cost(self.employee, self.activity_type)
|
||||||
|
if rate:
|
||||||
|
self.costing_rate = rate.get('costing_rate')
|
||||||
|
self.billing_rate = rate.get('billing_rate')
|
||||||
|
self.costing_amount = self.costing_rate * self.hours
|
||||||
|
if self.billable:
|
||||||
|
self.billing_amount = self.billing_rate * self.hours
|
||||||
|
else:
|
||||||
|
self.billing_amount = 0
|
||||||
|
|
||||||
|
def validate_task(self):
|
||||||
|
if self.project and not self.task:
|
||||||
|
frappe.throw(_("Task is Mandatory if Time Log is against a project"))
|
||||||
|
|
||||||
|
def update_task(self):
|
||||||
|
if self.task and frappe.db.exists("Task", self.task):
|
||||||
|
task = frappe.get_doc("Task", self.task)
|
||||||
|
task.update_time_and_costing()
|
||||||
|
task.save()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_events(start, end, filters=None):
|
def get_events(start, end, filters=None):
|
||||||
"""Returns events for Gantt / Calendar view rendering.
|
"""Returns events for Gantt / Calendar view rendering.
|
||||||
@@ -242,3 +268,9 @@ def get_events(start, end, filters=None):
|
|||||||
d.title += " for Project: " + d.project
|
d.title += " for Project: " + d.project
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_activity_cost(employee=None, activity_type=None):
|
||||||
|
rate = frappe.db.sql("""select costing_rate, billing_rate from `tabActivity Cost` where employee= %s
|
||||||
|
and activity_type= %s""", (employee, activity_type), as_dict=1)
|
||||||
|
return rate[0] if rate else {}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
// render
|
// render
|
||||||
frappe.listview_settings['Time Log'] = {
|
frappe.listview_settings['Time Log'] = {
|
||||||
add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "for_manufacturing"],
|
add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "for_manufacturing", "billing_amount"],
|
||||||
selectable: true,
|
selectable: true,
|
||||||
onload: function(me) {
|
onload: function(me) {
|
||||||
me.page.add_menu_item(__("Make Time Log Batch"), function() {
|
me.page.add_menu_item(__("Make Time Log Batch"), function() {
|
||||||
@@ -37,7 +37,7 @@ frappe.listview_settings['Time Log'] = {
|
|||||||
$.extend(detail, {
|
$.extend(detail, {
|
||||||
"time_log": d.name,
|
"time_log": d.name,
|
||||||
"activity_type": d.activity_type,
|
"activity_type": d.activity_type,
|
||||||
"created_by": d.owner,
|
"billing_amount": d.billing_amount,
|
||||||
"hours": d.hours
|
"hours": d.hours
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ def create_time_log():
|
|||||||
def create_time_log_batch(time_log):
|
def create_time_log_batch(time_log):
|
||||||
tlb = frappe.get_doc({
|
tlb = frappe.get_doc({
|
||||||
"doctype": "Time Log Batch",
|
"doctype": "Time Log Batch",
|
||||||
"rate": "500",
|
|
||||||
"time_logs": [
|
"time_logs": [
|
||||||
{
|
{
|
||||||
"doctype": "Time Log Batch Detail",
|
"doctype": "Time Log Batch Detail",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.add_fetch("time_log", "activity_type", "activity_type");
|
cur_frm.add_fetch("time_log", "activity_type", "activity_type");
|
||||||
cur_frm.add_fetch("time_log", "owner", "created_by");
|
cur_frm.add_fetch("time_log", "billing_amount", "billing_amount");
|
||||||
cur_frm.add_fetch("time_log", "hours", "hours");
|
cur_frm.add_fetch("time_log", "hours", "hours");
|
||||||
|
|
||||||
cur_frm.set_query("time_log", "time_logs", function(doc) {
|
cur_frm.set_query("time_log", "time_logs", function(doc) {
|
||||||
@@ -36,3 +36,15 @@ $.extend(cur_frm.cscript, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Time Log Batch Detail", "time_log", function(frm, cdt, cdn) {
|
||||||
|
var tl = frm.doc.time_logs || [];
|
||||||
|
total_hr = 0;
|
||||||
|
total_amt = 0;
|
||||||
|
for(var i=0; i<tl.length; i++) {
|
||||||
|
total_hr += tl[i].hours;
|
||||||
|
total_amt += tl[i].billing_amount;
|
||||||
|
}
|
||||||
|
cur_frm.set_value("total_hours", total_hr);
|
||||||
|
cur_frm.set_value("total_billing_amount", total_amt);
|
||||||
|
});
|
||||||
@@ -14,28 +14,22 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "For Sales Invoice",
|
|
||||||
"fieldname": "rate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Rate",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "Draft",
|
"default": "Draft",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
|
"no_copy": 1,
|
||||||
"options": "Draft\nSubmitted\nBilled\nCancelled",
|
"options": "Draft\nSubmitted\nBilled\nCancelled",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Will be updated after Sales Invoice is Submitted.",
|
"description": "Will be updated after Sales Invoice is Submitted.",
|
||||||
"fieldname": "sales_invoice",
|
"fieldname": "sales_invoice",
|
||||||
@@ -60,7 +54,14 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "In Hours",
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "updated via Time Logs",
|
||||||
"fieldname": "total_hours",
|
"fieldname": "total_hours",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -68,6 +69,22 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_10",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "updated via Time Logs",
|
||||||
|
"fieldname": "total_billing_amount",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Total Billing Amount",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -83,7 +100,7 @@
|
|||||||
"icon": "icon-time",
|
"icon": "icon-time",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2015-02-05 05:11:48.360822",
|
"modified": "2015-04-15 08:00:52.746961",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Time Log Batch",
|
"name": "Time Log Batch",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
from frappe.utils import flt
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
@@ -14,22 +15,24 @@ class TimeLogBatch(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.total_hours = 0.0
|
|
||||||
for d in self.get("time_logs"):
|
for d in self.get("time_logs"):
|
||||||
tl = frappe.get_doc("Time Log", d.time_log)
|
tl = frappe.get_doc("Time Log", d.time_log)
|
||||||
self.update_time_log_values(d, tl)
|
self.update_time_log_values(d, tl)
|
||||||
self.validate_time_log_is_submitted(tl)
|
self.validate_time_log_is_submitted(tl)
|
||||||
self.total_hours += float(tl.hours or 0.0)
|
self.total_hours += flt(tl.hours)
|
||||||
|
self.total_billing_amount += flt(tl.billing_amount)
|
||||||
|
|
||||||
def update_time_log_values(self, d, tl):
|
def update_time_log_values(self, d, tl):
|
||||||
d.update({
|
d.update({
|
||||||
"hours": tl.hours,
|
"hours": tl.hours,
|
||||||
"activity_type": tl.activity_type,
|
"activity_type": tl.activity_type,
|
||||||
"created_by": tl.owner
|
"billing_amount": tl.billing_amount
|
||||||
})
|
})
|
||||||
|
|
||||||
def validate_time_log_is_submitted(self, tl):
|
def validate_time_log_is_submitted(self, tl):
|
||||||
if tl.status != "Submitted" and self.docstatus == 0:
|
if tl.status == "Batched for Billing":
|
||||||
|
frappe.throw(_("Time Log {0} already billed").format(tl.name))
|
||||||
|
elif tl.status != "Submitted":
|
||||||
frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name))
|
frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name))
|
||||||
|
|
||||||
def set_status(self):
|
def set_status(self):
|
||||||
@@ -65,15 +68,15 @@ def make_sales_invoice(source_name, target=None):
|
|||||||
def update_item(source_doc, target_doc, source_parent):
|
def update_item(source_doc, target_doc, source_parent):
|
||||||
target_doc.stock_uom = "Hour"
|
target_doc.stock_uom = "Hour"
|
||||||
target_doc.description = "via Time Logs"
|
target_doc.description = "via Time Logs"
|
||||||
|
target_doc.qty = 1
|
||||||
|
|
||||||
target = frappe.new_doc("Sales Invoice")
|
target = frappe.new_doc("Sales Invoice")
|
||||||
target.append("items", get_mapped_doc("Time Log Batch", source_name, {
|
target.append("items", get_mapped_doc("Time Log Batch", source_name, {
|
||||||
"Time Log Batch": {
|
"Time Log Batch": {
|
||||||
"doctype": "Sales Invoice Item",
|
"doctype": "Sales Invoice Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"rate": "base_rate",
|
"total_billing_amount": "rate",
|
||||||
"name": "time_log_batch",
|
"name": "time_log_batch"
|
||||||
"total_hours": "qty",
|
|
||||||
},
|
},
|
||||||
"postprocess": update_item
|
"postprocess": update_item
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
frappe.listview_settings['Time Log Batch'] = {
|
frappe.listview_settings['Time Log Batch'] = {
|
||||||
add_fields: ["status", "total_hours", "rate"],
|
add_fields: ["status", "total_hours"],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
return [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
|
return [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"creation": "2013-03-05 09:11:06.000000",
|
"creation": "2013-03-05 09:11:06",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -15,14 +15,19 @@
|
|||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "created_by",
|
"fieldname": "hours",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Created By",
|
"label": "Hours",
|
||||||
"options": "User",
|
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_3",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "activity_type",
|
"fieldname": "activity_type",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -32,18 +37,21 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "hours",
|
"fieldname": "billing_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Hours",
|
"label": "Billing Amount",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2013-12-20 19:21:54.000000",
|
"modified": "2015-04-15 05:35:08.805589",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Time Log Batch Detail",
|
"name": "Time Log Batch Detail",
|
||||||
"owner": "Administrator"
|
"owner": "Administrator",
|
||||||
|
"permissions": []
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ def execute(filters=None):
|
|||||||
data.append([project.name, pr_item_map.get(project.name, 0),
|
data.append([project.name, pr_item_map.get(project.name, 0),
|
||||||
se_item_map.get(project.name, 0), dn_item_map.get(project.name, 0),
|
se_item_map.get(project.name, 0), dn_item_map.get(project.name, 0),
|
||||||
project.project_name, project.status, project.company,
|
project.project_name, project.status, project.company,
|
||||||
project.customer, project.project_value, project.project_start_date,
|
project.customer, project.estimated_costing, project.expected_start_date,
|
||||||
project.completion_date])
|
project.expected_end_date])
|
||||||
|
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ def get_columns():
|
|||||||
_("Project Start Date") + ":Date:120", _("Completion Date") + ":Date:120"]
|
_("Project Start Date") + ":Date:120", _("Completion Date") + ":Date:120"]
|
||||||
|
|
||||||
def get_project_details():
|
def get_project_details():
|
||||||
return frappe.db.sql(""" select name, project_name, status, company, customer, project_value,
|
return frappe.db.sql(""" select name, project_name, status, company, customer, estimated_costing,
|
||||||
project_start_date, completion_date from tabProject where docstatus < 2""", as_dict=1)
|
expected_start_date, expected_end_date from tabProject where docstatus < 2""", as_dict=1)
|
||||||
|
|
||||||
def get_purchased_items_cost():
|
def get_purchased_items_cost():
|
||||||
pr_items = frappe.db.sql("""select project_name, sum(base_net_amount) as amount
|
pr_items = frappe.db.sql("""select project_name, sum(base_net_amount) as amount
|
||||||
|
|||||||
Reference in New Issue
Block a user