[Enhancement] Time log wages and multiple active salary structure
This commit is contained in:
parent
18357664cb
commit
d6c986da8f
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Accounts Settings', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
});
|
@ -2,11 +2,13 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-06-24 15:49:57",
|
||||
"custom": 0,
|
||||
"description": "Settings for Accounts",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
@ -18,6 +20,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Make Accounting Entry For Every Stock Movement",
|
||||
@ -42,6 +45,7 @@
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Accounts Frozen Upto",
|
||||
@ -66,6 +70,7 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
|
||||
@ -91,6 +96,7 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Credit Controller",
|
||||
@ -115,6 +121,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Check Supplier Invoice Number Uniqueness",
|
||||
@ -136,13 +143,14 @@
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-12-24 21:42:01.274459",
|
||||
"modified": "2016-06-27 15:18:27.566087",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
@ -169,6 +177,9 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
@ -429,4 +429,30 @@ cur_frm.set_query("asset", "items", function(doc, cdt, cdn) {
|
||||
["Asset", "company", "=", doc.company]
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Sales Invoice', {
|
||||
setup: function(frm){
|
||||
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(doc, cdt, cdn){
|
||||
return {
|
||||
filters: [
|
||||
["Time Sheet", "status", "in", ["Submitted", "Payslip"]]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on('Sales Invoice Timesheet', {
|
||||
time_sheet: function(frm){
|
||||
frm.call({
|
||||
method: "calculate_billing_amount_from_timesheet",
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
refresh_field('total_billing_amount')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
cur_frm.add_fetch("time_sheet", "total_billing_amount", "billing_amount");
|
@ -1065,6 +1065,85 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": "",
|
||||
"depends_on": "eval:doc.total_billing_amount > 0",
|
||||
"fieldname": "time_sheet_list",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Time Sheet List",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "timesheets",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Time Sheets",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Sales Invoice Timesheet",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Total Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -3597,7 +3676,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-06-10 12:57:08.818701",
|
||||
"modified": "2016-06-27 15:13:36.523798",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -79,9 +79,10 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.set_against_income_account()
|
||||
self.validate_c_form()
|
||||
self.validate_time_logs_are_submitted()
|
||||
self.validate_time_sheets_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||
self.update_packing_list()
|
||||
self.calculate_billing_amount_from_timesheet()
|
||||
|
||||
def before_save(self):
|
||||
set_account_for_mode_of_payment(self)
|
||||
@ -125,11 +126,10 @@ class SalesInvoice(SellingController):
|
||||
if not cint(self.is_pos) == 1 and not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
|
||||
self.update_time_log_batch(self.name)
|
||||
|
||||
self.update_time_sheet(self.name)
|
||||
|
||||
def before_cancel(self):
|
||||
self.update_time_log_batch(None)
|
||||
self.update_time_sheet(None)
|
||||
|
||||
def on_cancel(self):
|
||||
self.check_close_sales_order("sales_order")
|
||||
@ -217,20 +217,21 @@ class SalesInvoice(SellingController):
|
||||
if pos:
|
||||
return {"print_format": pos.get("print_format") }
|
||||
|
||||
def update_time_log_batch(self, sales_invoice):
|
||||
for d in self.get("items"):
|
||||
if d.time_log_batch:
|
||||
tlb = frappe.get_doc("Time Log Batch", d.time_log_batch)
|
||||
tlb.sales_invoice = sales_invoice
|
||||
tlb.flags.ignore_validate_update_after_submit = True
|
||||
tlb.save()
|
||||
def update_time_sheet(self, sales_invoice):
|
||||
for d in self.get("timesheets"):
|
||||
if d.time_sheet:
|
||||
timesheet = frappe.get_doc("Time Sheet", d.time_sheet)
|
||||
timesheet.sales_invoice = sales_invoice
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.save()
|
||||
|
||||
def validate_time_logs_are_submitted(self):
|
||||
for d in self.get("items"):
|
||||
if d.time_log_batch:
|
||||
docstatus = frappe.db.get_value("Time Log Batch", d.time_log_batch, "docstatus")
|
||||
if docstatus!=1:
|
||||
frappe.throw(_("Time Log Batch {0} must be 'Submitted'").format(d.time_log_batch))
|
||||
def validate_time_sheets_are_submitted(self):
|
||||
for data in self.get("timesheets"):
|
||||
if data.time_sheet:
|
||||
status = frappe.db.get_value("Time Sheet", data.time_sheet, "status")
|
||||
if status not in ['Submitted', 'Payslip']:
|
||||
frappe.throw(_("Time Sheet {0} is already completed or cancelled").format(data.time_sheet))
|
||||
|
||||
def set_pos_fields(self, for_validate=False):
|
||||
"""Set retail related fields from POS Profiles"""
|
||||
@ -450,6 +451,13 @@ class SalesInvoice(SellingController):
|
||||
else:
|
||||
self.set('packed_items', [])
|
||||
|
||||
def calculate_billing_amount_from_timesheet(self):
|
||||
total_billing_amount = 0.0
|
||||
for data in self.timesheets:
|
||||
if data.billing_amount:
|
||||
total_billing_amount += data.billing_amount
|
||||
|
||||
self.total_billing_amount = total_billing_amount
|
||||
|
||||
def get_warehouse(self):
|
||||
user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
|
||||
|
@ -3,6 +3,7 @@
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -1417,31 +1418,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "time_log_batch",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Time Log Batch",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Time Log Batch",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -1676,18 +1652,20 @@
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-04-06 12:26:19.052860",
|
||||
"modified": "2016-06-24 16:01:59.719026",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
|
@ -0,0 +1,87 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-14 19:21:34.321662",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "time_sheet",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Time Sheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Time Sheet",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-06-15 23:56:06.131202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Timesheet",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SalesInvoiceTimesheet(Document):
|
||||
pass
|
@ -3,4 +3,4 @@
|
||||
Now additional costs like shipping charges, operating costs etc can be added in Stock Entry in item valuation
|
||||
- **Update Finished Goods** in Production Order can now use the items from **Transfer Materials for Manufacture** step instead of items from the Bill of Materials. This can be configured in Manufacturing Settings
|
||||
- Added field **Tax ID** in Customer
|
||||
- Bug fixes in Item, Time Log Batch, Pricing Rule, Salary Slip, Address and Stock Entry
|
||||
- Bug fixes in Item, Time Sheet, Pricing Rule, Salary Slip, Address and Stock Entry
|
||||
|
@ -23,8 +23,8 @@ def get_data():
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Time Log",
|
||||
"description": _("Time Logs for manufacturing."),
|
||||
"name": "Time Sheet",
|
||||
"description": _("Time Sheet for manufacturing."),
|
||||
},
|
||||
|
||||
]
|
||||
|
@ -31,13 +31,8 @@ def get_data():
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Time Log",
|
||||
"description": _("Time Log for tasks."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Time Log Batch",
|
||||
"description": _("Batch Time Logs for billing."),
|
||||
"name": "Time Sheet",
|
||||
"description": _("Time Sheet for tasks."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
@ -58,8 +53,8 @@ def get_data():
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Daily Time Log Summary",
|
||||
"doctype": "Time Log"
|
||||
"name": "Daily Time Sheet Summary",
|
||||
"doctype": "Time Sheet"
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
@ -9,17 +9,17 @@ ODER
|
||||
|
||||
Öffnen Sie einfach Ihre Übersicht der Zeitprotokolle und kreuzen Sie die Artikel an, die Sie dem Zeitprotokollstapel hinzufügen möchten. Klicken Sie dann auf die Schaltfläche "Zeitprotokollstapel erstellen" und diese Zeitprotokolle werden ausgewählt.
|
||||
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_log_batch.gif">
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_sheet.gif">
|
||||
|
||||
### Ausgangsrechnungen erstellen
|
||||
|
||||
* Sobald Sie einen Zeitprotokollstapel übertragen haben, sollte die Schaltfläche "Rechnung erstellen" erscheinen.
|
||||
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_log_batch_make_invoice.png">
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_sheet_make_invoice.png">
|
||||
|
||||
* Klicken Sie auf diese Schaltfläche und erstellen Sie eine Ausgangsrechnung zu einem Zeitprotokollstapel.
|
||||
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_log_batch_sales_invoice.png">
|
||||
<img class="screenshot" alt="Zeitprotokoll - Kalender aufziehen" src="{{docs_base_url}}/assets/img/project/time_sheet_sales_invoice.png">
|
||||
|
||||
* Wenn Sie die Ausgangsrechnung "übertragen", wird die Nummer der Ausgangsrechnung in den Zeitprotokollen und im Zeitprotokollstapel aktualisiert und ihr Status wird auf "abgerechnet" geändert.
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
You can bill Time Logs by batching them together. This gives you the flexiblity to manage your customer billing in the way you want. To create a new Time Log Batch, go to
|
||||
You can bill Time Logs by batching them together. This gives you the flexiblity to manage your customer billing in the way you want. To create a new Time Sheet, go to
|
||||
|
||||
> Projects > Time Log Batch > New Time Log Batch
|
||||
> Projects > Time Sheet > New Time Sheet
|
||||
|
||||
OR
|
||||
|
||||
Just open your Time Log list and check the Items to you want to add to the Time Log. Then click on "Make Time Log Batch" button and these Time Logs will be selected.
|
||||
Just open your Time Log list and check the Items to you want to add to the Time Log. Then click on "Make Time Sheet" button and these Time Logs will be selected.
|
||||
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_log_batch.gif">
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_sheet.gif">
|
||||
|
||||
###Making Sales Invoice
|
||||
|
||||
* After submitting the Time Log Batch, "Make Invoice" button shall appear.
|
||||
* After submitting the Time Sheet, "Make Invoice" button shall appear.
|
||||
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_log_batch_make_invoice.png">
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_sheet_make_invoice.png">
|
||||
|
||||
* Click on that button to raise a Sales Invoice against the Time Log Batch.
|
||||
* Click on that button to raise a Sales Invoice against the Time Sheet.
|
||||
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_log_batch_sales_invoice.png">
|
||||
<img class="screenshot" alt="Time Log - Drag Calender" src="{{docs_base_url}}/assets/img/project/time_sheet_sales_invoice.png">
|
||||
|
||||
* When you "Submit" the Sales Invoice, the Sales Invoice number will get updated in the Time Logs and Time Log Batch and their status will change to "Billed".
|
||||
* When you "Submit" the Sales Invoice, the Sales Invoice number will get updated in the Time Logs and Time Sheet and their status will change to "Billed".
|
||||
|
||||
{next}
|
@ -41,7 +41,7 @@ my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
|
||||
|
||||
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
|
||||
|
||||
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order", "Holiday List"]
|
||||
calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Holiday List"]
|
||||
|
||||
fixtures = ["Web Form"]
|
||||
|
||||
|
@ -9,7 +9,7 @@ links = {
|
||||
},
|
||||
{
|
||||
'label': _('Payroll'),
|
||||
'items': ['Salary Structure', 'Salary Slip']
|
||||
'items': ['Salary Structure', 'Salary Slip', 'Time Sheet']
|
||||
},
|
||||
{
|
||||
'label': _('Expense'),
|
||||
|
@ -166,7 +166,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "1",
|
||||
"description": "Check if you want to send salary slip in mail to each employee while submitting salary slip",
|
||||
"description": "",
|
||||
"fieldname": "email_salary_slip_to_employee",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -200,7 +200,7 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-06-25 17:43:06.643469",
|
||||
"modified": "2016-06-27 16:20:59.737869",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR Settings",
|
||||
@ -220,8 +220,6 @@
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"restrict": 0,
|
||||
"restricted": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
|
@ -58,7 +58,8 @@ cur_frm.cscript.make_jv = function(doc, dt, dn) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
frappe.ui.form.on("Process Payroll", "refresh", function(frm) {
|
||||
frm.disable_save();
|
||||
});
|
||||
frappe.ui.form.on("Process Payroll", {
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
}
|
||||
})
|
||||
|
@ -2,6 +2,7 @@
|
||||
"allow_copy": 1,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2012-03-27 14:35:59",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -180,6 +181,55 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "salary_slip_based_on_timesheet",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Salary Slip Based on Timesheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -503,13 +553,14 @@
|
||||
"hide_toolbar": 1,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-04-26 07:22:41.792785",
|
||||
"modified": "2016-06-22 18:14:02.418857",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Process Payroll",
|
||||
@ -536,8 +587,10 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
@ -15,20 +15,18 @@ class ProcessPayroll(Document):
|
||||
Returns list of active employees based on selected criteria
|
||||
and for which salary structure exists
|
||||
"""
|
||||
|
||||
cond = self.get_filter_condition()
|
||||
cond += self.get_joining_releiving_condition()
|
||||
|
||||
emp_list = frappe.db.sql("""
|
||||
select t1.name
|
||||
from `tabEmployee` t1, `tabSalary Structure` t2
|
||||
where t1.docstatus!=2 and t2.docstatus != 2
|
||||
and t1.name = t2.employee
|
||||
where t1.docstatus!=2 and t2.docstatus != 2 and
|
||||
ifnull(t2.salary_slip_based_on_timesheet,0) = 0 and t1.name = t2.employee
|
||||
%s """% cond)
|
||||
|
||||
return emp_list
|
||||
|
||||
|
||||
def get_filter_condition(self):
|
||||
self.check_mandatory()
|
||||
|
||||
@ -67,6 +65,7 @@ class ProcessPayroll(Document):
|
||||
""", (emp[0], self.month, self.fiscal_year, self.company)):
|
||||
ss = frappe.get_doc({
|
||||
"doctype": "Salary Slip",
|
||||
"salary_slip_based_on_timesheet": 0,
|
||||
"fiscal_year": self.fiscal_year,
|
||||
"employee": emp[0],
|
||||
"month": self.month,
|
||||
|
@ -2,13 +2,51 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.add_fetch('employee', 'company', 'company');
|
||||
cur_frm.add_fetch('time_sheet', 'total_hours', 'working_hours');
|
||||
|
||||
frappe.ui.form.on("Salary Slip", {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
|
||||
return {
|
||||
filters: {
|
||||
employee: frm.doc.employee,
|
||||
make_for: 'Salary Slip'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
var company = locals[':Company'][frm.doc.company];
|
||||
if(!frm.doc.letter_head && company.default_letter_head) {
|
||||
frm.set_value('letter_head', company.default_letter_head);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
},
|
||||
|
||||
salary_slip_based_on_timesheet: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
},
|
||||
|
||||
toggle_fields: function(frm) {
|
||||
if(frm.doc.salary_slip_based_on_timesheet) {
|
||||
hide_field(['fiscal_year', 'month', 'total_days_in_month', 'leave_without_pay', 'payment_days'])
|
||||
unhide_field(['start_date', 'end_date', 'hourly_wages', 'timesheets'])
|
||||
}else {
|
||||
unhide_field(['fiscal_year', 'month', 'total_days_in_month', 'leave_without_pay', 'payment_days'])
|
||||
hide_field(['start_date', 'end_date', 'hourly_wages', 'timesheets'])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
frappe.ui.form.on("Salary Slip Timesheet", {
|
||||
time_sheet: function(frm, cdt, cdn) {
|
||||
doc = frm.doc;
|
||||
cur_frm.cscript.fiscal_year(doc, cdt, cdn)
|
||||
}
|
||||
})
|
||||
|
||||
@ -38,7 +76,13 @@ cur_frm.cscript.fiscal_year = function(doc,dt,dn){
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.cscript.month = cur_frm.cscript.employee = cur_frm.cscript.fiscal_year;
|
||||
cur_frm.cscript.month = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.fiscal_year;
|
||||
cur_frm.cscript.start_date = cur_frm.cscript.end_date = cur_frm.cscript.fiscal_year;
|
||||
|
||||
cur_frm.cscript.employee = function(doc,dt,dn){
|
||||
doc.salary_structure = ''
|
||||
cur_frm.cscript.fiscal_year(doc, dt, dn)
|
||||
}
|
||||
|
||||
cur_frm.cscript.leave_without_pay = function(doc,dt,dn){
|
||||
if (doc.employee && doc.fiscal_year && doc.month) {
|
||||
@ -56,7 +100,7 @@ var calculate_all = function(doc, dt, dn) {
|
||||
calculate_net_pay(doc, dt, dn);
|
||||
}
|
||||
|
||||
cur_frm.cscript.e_modified_amount = function(doc,dt,dn){
|
||||
cur_frm.cscript.earning_amount = function(doc,dt,dn){
|
||||
calculate_earning_total(doc, dt, dn);
|
||||
calculate_net_pay(doc, dt, dn);
|
||||
}
|
||||
@ -67,7 +111,7 @@ cur_frm.cscript.e_depends_on_lwp = function(doc,dt,dn){
|
||||
}
|
||||
// Trigger on earning modified amount and depends on lwp
|
||||
// ------------------------------------------------------------------------
|
||||
cur_frm.cscript.d_modified_amount = function(doc,dt,dn){
|
||||
cur_frm.cscript.deduction_amount = function(doc,dt,dn){
|
||||
calculate_ded_total(doc, dt, dn);
|
||||
calculate_net_pay(doc, dt, dn);
|
||||
}
|
||||
@ -85,17 +129,17 @@ var calculate_earning_total = function(doc, dt, dn, reset_amount) {
|
||||
var total_earn = 0;
|
||||
for(var i = 0; i < tbl.length; i++){
|
||||
if(cint(tbl[i].e_depends_on_lwp) == 1) {
|
||||
tbl[i].e_modified_amount = Math.round(tbl[i].e_amount)*(flt(doc.payment_days) /
|
||||
tbl[i].earning_amount = Math.round(tbl[i].e_amount)*(flt(doc.payment_days) /
|
||||
cint(doc.total_days_in_month)*100)/100;
|
||||
refresh_field('e_modified_amount', tbl[i].name, 'earnings');
|
||||
refresh_field('earning_amount', tbl[i].name, 'earnings');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].e_modified_amount = tbl[i].e_amount;
|
||||
refresh_field('e_modified_amount', tbl[i].name, 'earnings');
|
||||
tbl[i].earning_amount = tbl[i].e_amount;
|
||||
refresh_field('earning_amount', tbl[i].name, 'earnings');
|
||||
}
|
||||
total_earn += flt(tbl[i].e_modified_amount);
|
||||
total_earn += flt(tbl[i].earning_amount);
|
||||
}
|
||||
doc.gross_pay = total_earn + flt(doc.arrear_amount) + flt(doc.leave_encashment_amount);
|
||||
refresh_many(['e_modified_amount', 'gross_pay']);
|
||||
refresh_many(['earning_amount', 'gross_pay']);
|
||||
}
|
||||
|
||||
// Calculate deduction total
|
||||
@ -106,13 +150,13 @@ var calculate_ded_total = function(doc, dt, dn, reset_amount) {
|
||||
var total_ded = 0;
|
||||
for(var i = 0; i < tbl.length; i++){
|
||||
if(cint(tbl[i].d_depends_on_lwp) == 1) {
|
||||
tbl[i].d_modified_amount = Math.round(tbl[i].d_amount)*(flt(doc.payment_days)/cint(doc.total_days_in_month)*100)/100;
|
||||
refresh_field('d_modified_amount', tbl[i].name, 'deductions');
|
||||
tbl[i].deduction_amount = Math.round(tbl[i].d_amount)*(flt(doc.payment_days)/cint(doc.total_days_in_month)*100)/100;
|
||||
refresh_field('deduction_amount', tbl[i].name, 'deductions');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].d_modified_amount = tbl[i].d_amount;
|
||||
refresh_field('d_modified_amount', tbl[i].name, 'earnings');
|
||||
tbl[i].deduction_amount = tbl[i].d_amount;
|
||||
refresh_field('deduction_amount', tbl[i].name, 'earnings');
|
||||
}
|
||||
total_ded += flt(tbl[i].d_modified_amount);
|
||||
total_ded += flt(tbl[i].deduction_amount);
|
||||
}
|
||||
doc.total_deduction = total_ded;
|
||||
refresh_field('total_deduction');
|
||||
|
@ -2,6 +2,7 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:15",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -65,7 +66,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@ -76,10 +77,11 @@
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "employee_name",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "employee.employee_name",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
@ -167,6 +169,31 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -221,8 +248,8 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@ -230,8 +257,8 @@
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
@ -239,41 +266,40 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "month",
|
||||
"fieldtype": "Select",
|
||||
"depends_on": "",
|
||||
"fieldname": "salary_slip_based_on_timesheet",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Month",
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Salary Slip Based on Timesheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "month",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "37%"
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -301,6 +327,139 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "month",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Month",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "month",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "37%"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "Today",
|
||||
"depends_on": "",
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "End Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "salary_structure",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Salary Structure",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Salary Structure",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "total_days_in_month",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
@ -327,6 +486,7 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "leave_without_pay",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
@ -353,6 +513,7 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "payment_days",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
@ -375,6 +536,159 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "hourly_wages",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "timesheets",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Salary Slip Timesheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Salary Slip Timesheet",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_20",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "total_working_hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Total Working Hours",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Hour Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "section_break_26",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -427,6 +741,30 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_01",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -610,52 +948,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_26",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -737,6 +1029,29 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -849,13 +1164,14 @@
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-file-text",
|
||||
"idx": 9,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-04-26 06:02:06.940543",
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-06-27 16:22:46.063078",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Slip",
|
||||
@ -923,6 +1239,7 @@
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
|
@ -20,14 +20,16 @@ class SalarySlip(TransactionBase):
|
||||
self.name = make_autoname('Sal Slip/' +self.employee + '/.#####')
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.check_existing()
|
||||
self.set_month_dates()
|
||||
|
||||
if not (len(self.get("earnings")) or len(self.get("deductions"))):
|
||||
self.get_emp_and_leave_details()
|
||||
else:
|
||||
self.get_leave_details(lwp = self.leave_without_pay)
|
||||
|
||||
if not self.net_pay:
|
||||
if self.salary_slip_based_on_timesheet or not self.net_pay:
|
||||
self.calculate_net_pay()
|
||||
|
||||
company_currency = get_company_currency(self.company)
|
||||
@ -35,38 +37,85 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
set_employee_name(self)
|
||||
|
||||
def validate_dates(self):
|
||||
if date_diff(self.end_date, self.start_date) < 0:
|
||||
frappe.throw(_("To date cannot be before From date"))
|
||||
|
||||
def get_emp_and_leave_details(self):
|
||||
if self.employee:
|
||||
self.set("earnings", [])
|
||||
self.set("deductions", [])
|
||||
|
||||
self.set_month_dates()
|
||||
self.validate_dates()
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
self.get_leave_details(joining_date, relieving_date)
|
||||
|
||||
struct = self.check_sal_struct(joining_date, relieving_date)
|
||||
|
||||
if struct:
|
||||
self.set("earnings", [])
|
||||
self.set("deduction", [])
|
||||
self.pull_sal_struct(struct)
|
||||
ss_doc = frappe.get_doc('Salary Structure', struct)
|
||||
self.salary_slip_based_on_timesheet = ss_doc.salary_slip_based_on_timesheet or 0
|
||||
self.set_time_sheet()
|
||||
self.pull_sal_struct(ss_doc)
|
||||
|
||||
def set_time_sheet(self):
|
||||
if self.salary_slip_based_on_timesheet and not self.get('timesheets'):
|
||||
self.set("timesheets", [])
|
||||
|
||||
timesheets = frappe.db.sql(""" select * from `tabTime Sheet` where employee = %(employee)s and (status = 'Submitted' or
|
||||
status = 'Billed')""", {'employee': self.employee}, as_dict=1)
|
||||
|
||||
for data in timesheets:
|
||||
self.append('timesheets', {
|
||||
'time_sheet': data.name,
|
||||
'working_hours': data.total_hours
|
||||
})
|
||||
|
||||
def set_month_dates(self):
|
||||
if self.month and not self.salary_slip_based_on_timesheet:
|
||||
m = get_month_details(self.fiscal_year, self.month)
|
||||
self.start_date = m['month_start_date']
|
||||
self.end_date = m['month_end_date']
|
||||
|
||||
def check_sal_struct(self, joining_date, relieving_date):
|
||||
m = get_month_details(self.fiscal_year, self.month)
|
||||
|
||||
timesheet = 1 if self.salary_slip_based_on_timesheet else 0
|
||||
struct = frappe.db.sql("""select name from `tabSalary Structure`
|
||||
where employee=%s and is_active = 'Yes'
|
||||
and (from_date <= %s or from_date <= %s)
|
||||
and (to_date is null or to_date >= %s or to_date >= %s)""",
|
||||
(self.employee, m.month_start_date, joining_date, m.month_end_date, relieving_date))
|
||||
and (to_date is null or to_date >= %s or to_date >= %s) and salary_slip_based_on_timesheet=%s""",
|
||||
(self.employee, self.start_date, joining_date, self.end_date, relieving_date, timesheet))
|
||||
|
||||
if not struct:
|
||||
msgprint(_("No active Salary Structure found for employee {0} and the month")
|
||||
self.salary_structure = None
|
||||
msgprint(_("No active or default Salary Structure found for employee {0} and the month")
|
||||
.format(self.employee))
|
||||
self.employee = None
|
||||
|
||||
return struct and struct[0][0] or ''
|
||||
|
||||
def pull_sal_struct(self, struct):
|
||||
def pull_sal_struct(self, ss_doc):
|
||||
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
make_salary_slip(struct, self)
|
||||
make_salary_slip(ss_doc.name, self)
|
||||
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.salary_structure = ss_doc.name
|
||||
self.hour_rate = ss_doc.hour_rate
|
||||
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
|
||||
self.add_earning_for_hourly_wages(ss_doc.earning_type)
|
||||
|
||||
def add_earning_for_hourly_wages(self, earning_type):
|
||||
default_type = False
|
||||
for data in self.earnings:
|
||||
if data.earning_type == earning_type:
|
||||
data.earning_amount = self.hour_rate * self.total_working_hours
|
||||
default_type = True
|
||||
break
|
||||
|
||||
if not default_type:
|
||||
earnings = self.append('earnings', {})
|
||||
earnings.earning_type = earning_type
|
||||
earnings.earning_amount = self.hour_rate * self.total_working_hours
|
||||
|
||||
def pull_emp_details(self):
|
||||
emp = frappe.db.get_value("Employee", self.employee, ["bank_name", "bank_ac_no"], as_dict=1)
|
||||
@ -81,40 +130,41 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if not self.month:
|
||||
self.month = "%02d" % getdate(nowdate()).month
|
||||
self.set_month_dates()
|
||||
|
||||
if not joining_date:
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
m = get_month_details(self.fiscal_year, self.month)
|
||||
holidays = self.get_holidays_for_employee(m['month_start_date'], m['month_end_date'])
|
||||
holidays = self.get_holidays_for_employee(self.start_date, self.end_date)
|
||||
|
||||
working_days = m["month_days"]
|
||||
working_days = date_diff(self.end_date, self.start_date) + 1
|
||||
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
|
||||
working_days -= len(holidays)
|
||||
if working_days < 0:
|
||||
frappe.throw(_("There are more holidays than working days this month."))
|
||||
|
||||
if not lwp:
|
||||
lwp = self.calculate_lwp(holidays, m)
|
||||
lwp = self.calculate_lwp(holidays, working_days)
|
||||
self.total_days_in_month = working_days
|
||||
self.leave_without_pay = lwp
|
||||
payment_days = flt(self.get_payment_days(m, joining_date, relieving_date)) - flt(lwp)
|
||||
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp)
|
||||
self.payment_days = payment_days > 0 and payment_days or 0
|
||||
|
||||
def get_payment_days(self, month, joining_date, relieving_date):
|
||||
start_date = month['month_start_date']
|
||||
def get_payment_days(self, joining_date, relieving_date):
|
||||
start_date = getdate(self.start_date)
|
||||
|
||||
if joining_date:
|
||||
if joining_date > month['month_start_date']:
|
||||
if joining_date > getdate(self.start_date):
|
||||
start_date = joining_date
|
||||
elif joining_date > month['month_end_date']:
|
||||
elif joining_date > getdate(self.end_date):
|
||||
return
|
||||
|
||||
end_date = month['month_end_date']
|
||||
end_date = getdate(self.end_date)
|
||||
if relieving_date:
|
||||
if relieving_date > start_date and relieving_date < month['month_end_date']:
|
||||
if relieving_date > start_date and relieving_date < getdate(self.end_date):
|
||||
end_date = relieving_date
|
||||
elif relieving_date < month['month_start_date']:
|
||||
elif relieving_date < getdate(self.start_date):
|
||||
frappe.throw(_("Employee relieved on {0} must be set as 'Left'")
|
||||
.format(relieving_date))
|
||||
|
||||
@ -142,10 +192,10 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
return holidays
|
||||
|
||||
def calculate_lwp(self, holidays, m):
|
||||
def calculate_lwp(self, holidays, working_days):
|
||||
lwp = 0
|
||||
for d in range(m['month_days']):
|
||||
dt = add_days(cstr(m['month_start_date']), d)
|
||||
for d in range(working_days):
|
||||
dt = add_days(cstr(self.start_date), d)
|
||||
if dt not in holidays:
|
||||
leave = frappe.db.sql("""
|
||||
select t1.name, t1.half_day
|
||||
@ -161,38 +211,43 @@ class SalarySlip(TransactionBase):
|
||||
return lwp
|
||||
|
||||
def check_existing(self):
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where month = %s and fiscal_year = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
(self.month, self.fiscal_year, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for this month").format(self.employee))
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where month = %s and fiscal_year = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
(self.month, self.fiscal_year, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee))
|
||||
else:
|
||||
for data in self.timesheets:
|
||||
if frappe.db.get_value('Time Sheet', data.time_sheet, 'status') == 'Payrolled':
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
|
||||
|
||||
def calculate_earning_total(self):
|
||||
self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
|
||||
for d in self.get("earnings"):
|
||||
if cint(d.e_depends_on_lwp) == 1:
|
||||
d.e_modified_amount = rounded((flt(d.e_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("e_modified_amount", "earnings"))
|
||||
d.earning_amount = rounded((flt(d.e_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("earning_amount", "earnings"))
|
||||
elif not self.payment_days:
|
||||
d.e_modified_amount = 0
|
||||
elif not d.e_modified_amount:
|
||||
d.e_modified_amount = d.e_amount
|
||||
self.gross_pay += flt(d.e_modified_amount)
|
||||
d.earning_amount = 0
|
||||
elif not d.earning_amount:
|
||||
d.earning_amount = d.e_amount
|
||||
self.gross_pay += flt(d.earning_amount)
|
||||
|
||||
def calculate_ded_total(self):
|
||||
self.total_deduction = 0
|
||||
for d in self.get('deductions'):
|
||||
if cint(d.d_depends_on_lwp) == 1:
|
||||
d.d_modified_amount = rounded((flt(d.d_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("d_modified_amount", "deductions"))
|
||||
d.deduction_amount = rounded((flt(d.d_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("deduction_amount", "deductions"))
|
||||
elif not self.payment_days:
|
||||
d.d_modified_amount = 0
|
||||
elif not d.d_modified_amount:
|
||||
d.d_modified_amount = d.d_amount
|
||||
d.deduction_amount = 0
|
||||
elif not d.deduction_amount:
|
||||
d.deduction_amount = d.d_amount
|
||||
|
||||
self.total_deduction += flt(d.d_modified_amount)
|
||||
self.total_deduction += flt(d.deduction_amount)
|
||||
|
||||
def calculate_net_pay(self):
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
|
||||
@ -204,16 +259,28 @@ class SalarySlip(TransactionBase):
|
||||
self.precision("net_pay") if disable_rounded_total else 0)
|
||||
|
||||
def on_submit(self):
|
||||
self.update_status(self.name)
|
||||
if(frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee")):
|
||||
self.email_salary_slip()
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_status()
|
||||
|
||||
def email_salary_slip(self):
|
||||
receiver = frappe.db.get_value("Employee", self.employee, "company_email") or \
|
||||
frappe.db.get_value("Employee", self.employee, "personal_email")
|
||||
if receiver:
|
||||
subj = 'Salary Slip - ' + cstr(self.month) +'/'+cstr(self.fiscal_year)
|
||||
subj = 'Salary Slip - from {0} to {1}, fiscal year {2}'.format(self.start_date, self.end_date, self.fiscal_year)
|
||||
frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"),
|
||||
attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name)
|
||||
else:
|
||||
msgprint(_("{0}: Employee email not found, hence email not sent").format(self.employee_name))
|
||||
|
||||
def update_status(self, salary_slip=None):
|
||||
for data in self.timesheets:
|
||||
if data.time_sheet:
|
||||
timesheet = frappe.get_doc('Time Sheet', data.time_sheet)
|
||||
timesheet.salary_slip = salary_slip
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.save()
|
@ -5,14 +5,14 @@
|
||||
{
|
||||
"d_amount": 100,
|
||||
"d_depends_on_lwp": 0,
|
||||
"d_type": "_Test Professional Tax",
|
||||
"deduction_type": "_Test Professional Tax",
|
||||
"doctype": "Salary Slip Deduction",
|
||||
"parentfield": "deductions"
|
||||
},
|
||||
{
|
||||
"d_amount": 50,
|
||||
"d_depends_on_lwp": 1,
|
||||
"d_type": "_Test TDS",
|
||||
"deduction_type": "_Test TDS",
|
||||
"doctype": "Salary Slip Deduction",
|
||||
"parentfield": "deductions"
|
||||
}
|
||||
@ -23,14 +23,14 @@
|
||||
"doctype": "Salary Slip Earning",
|
||||
"e_amount": 15000,
|
||||
"e_depends_on_lwp": 1,
|
||||
"e_type": "_Test Basic Salary",
|
||||
"earning_type": "_Test Basic Salary",
|
||||
"parentfield": "earnings"
|
||||
},
|
||||
{
|
||||
"doctype": "Salary Slip Earning",
|
||||
"e_amount": 500,
|
||||
"e_depends_on_lwp": 0,
|
||||
"e_type": "_Test Allowance",
|
||||
"earning_type": "_Test Allowance",
|
||||
"parentfield": "earnings"
|
||||
}
|
||||
],
|
||||
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import today
|
||||
from frappe.utils import today, now_datetime, getdate, cstr
|
||||
from erpnext.hr.doctype.employee.employee import make_salary_structure
|
||||
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
|
||||
@ -35,10 +35,10 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
self.assertEquals(ss.total_days_in_month, 31)
|
||||
self.assertEquals(ss.payment_days, 30)
|
||||
self.assertEquals(ss.earnings[0].e_modified_amount, 14516.13)
|
||||
self.assertEquals(ss.earnings[1].e_modified_amount, 500)
|
||||
self.assertEquals(ss.deductions[0].d_modified_amount, 100)
|
||||
self.assertEquals(ss.deductions[1].d_modified_amount, 48.39)
|
||||
self.assertEquals(ss.earnings[0].earning_amount, 14516.13)
|
||||
self.assertEquals(ss.earnings[1].earning_amount, 500)
|
||||
self.assertEquals(ss.deductions[0].deduction_amount, 100)
|
||||
self.assertEquals(ss.deductions[1].deduction_amount, 48.39)
|
||||
self.assertEquals(ss.gross_pay, 15016.13)
|
||||
self.assertEquals(ss.net_pay, 14867.74)
|
||||
|
||||
@ -49,10 +49,10 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
self.assertEquals(ss.total_days_in_month, 29)
|
||||
self.assertEquals(ss.payment_days, 28)
|
||||
self.assertEquals(ss.earnings[0].e_modified_amount, 14482.76)
|
||||
self.assertEquals(ss.earnings[1].e_modified_amount, 500)
|
||||
self.assertEquals(ss.deductions[0].d_modified_amount, 100)
|
||||
self.assertEquals(ss.deductions[1].d_modified_amount, 48.28)
|
||||
self.assertEquals(ss.earnings[0].earning_amount, 14482.76)
|
||||
self.assertEquals(ss.earnings[1].earning_amount, 500)
|
||||
self.assertEquals(ss.deductions[0].deduction_amount, 100)
|
||||
self.assertEquals(ss.deductions[1].deduction_amount, 48.28)
|
||||
self.assertEquals(ss.gross_pay, 14982.76)
|
||||
self.assertEquals(ss.net_pay, 14834.48)
|
||||
|
||||
@ -153,6 +153,13 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
return salary_slip
|
||||
|
||||
def make_activity_for_employee(self):
|
||||
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
|
||||
activity_type.billing_rate = 50
|
||||
activity_type.costing_rate = 20
|
||||
activity_type.wage_rate = 25
|
||||
activity_type.save()
|
||||
|
||||
test_dependencies = ["Leave Application", "Holiday List"]
|
||||
|
||||
test_records = frappe.get_test_records('Salary Slip')
|
||||
|
@ -2,19 +2,22 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:48",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "d_type",
|
||||
"fieldname": "deduction_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
@ -25,6 +28,7 @@
|
||||
"options": "Deduction Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "200px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
@ -42,6 +46,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Default Amount",
|
||||
@ -52,6 +57,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -67,6 +73,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Depends on Leave Without Pay",
|
||||
@ -74,6 +81,7 @@
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -85,10 +93,11 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "d_modified_amount",
|
||||
"fieldname": "deduction_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
@ -97,6 +106,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -108,18 +118,22 @@
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:55.610195",
|
||||
"modified": "2016-06-27 16:02:25.613138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Slip Deduction",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
@ -2,19 +2,22 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:48",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "e_type",
|
||||
"fieldname": "earning_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
@ -25,6 +28,7 @@
|
||||
"options": "Earning Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
@ -42,6 +46,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Default Amount",
|
||||
@ -52,6 +57,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -67,6 +73,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Depends on Leave Without Pay",
|
||||
@ -74,6 +81,7 @@
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -85,10 +93,11 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "e_modified_amount",
|
||||
"fieldname": "earning_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
@ -97,6 +106,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -108,18 +118,22 @@
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:55.649418",
|
||||
"modified": "2016-06-27 15:56:04.146109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Slip Earning",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-14 19:22:29.811658",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "time_sheet",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Time Sheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Time Sheet",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "working_hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Working Hours",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "3",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-06-15 17:59:57.876373",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Slip Timesheet",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SalarySlipTimesheet(Document):
|
||||
pass
|
@ -12,7 +12,7 @@ cur_frm.cscript.onload = function(doc, dt, dn){
|
||||
}
|
||||
|
||||
cur_frm.cscript.refresh = function(doc, dt, dn){
|
||||
if((!doc.__islocal) && (doc.is_active == 'Yes')){
|
||||
if((!doc.__islocal) && (doc.is_active == 'Yes') && (doc.salary_slip_based_on_timesheet == 0)){
|
||||
cur_frm.add_custom_button(__('Salary Slip'),
|
||||
cur_frm.cscript['Make Salary Slip'], __("Make"));
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
|
||||
@ -52,7 +52,11 @@ var calculate_totals = function(doc, cdt, cdn) {
|
||||
}
|
||||
doc.total_earning = total_earn;
|
||||
doc.total_deduction = total_ded;
|
||||
doc.net_pay = flt(total_earn) - flt(total_ded);
|
||||
doc.net_pay = 0.0
|
||||
if(doc.salary_slip_based_on_timesheet == 0){
|
||||
doc.net_pay = flt(total_earn) - flt(total_ded);
|
||||
}
|
||||
|
||||
refresh_many(['total_earning', 'total_deduction', 'net_pay']);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-03-07 18:50:29",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -269,6 +270,33 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "No",
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Is Default",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Yes\nNo",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -325,6 +353,136 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "salary_slip_based_on_timesheet",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Salary Slip Based on Timesheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:doc.salary_slip_based_on_timesheet",
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"description": "Earning type for timesheet based payroll.",
|
||||
"fieldname": "earning_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Earning Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Earning Type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Hour Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"description": "Salary breakup based on Earning and Deduction.",
|
||||
"fieldname": "earning_deduction",
|
||||
"fieldtype": "Section Break",
|
||||
@ -333,7 +491,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Monthly Earning & Deduction",
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "earning_deduction",
|
||||
@ -379,6 +537,7 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "earnings",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
@ -397,7 +556,7 @@
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
@ -460,7 +619,8 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break0",
|
||||
"depends_on": "",
|
||||
"fieldname": "net_pay_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -612,13 +772,14 @@
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-03-18 16:10:30.031811",
|
||||
"modified": "2016-06-27 16:23:16.856237",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Structure",
|
||||
@ -649,7 +810,7 @@
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
@ -665,10 +826,12 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"timeline_field": "employee",
|
||||
"title_field": "employee_name"
|
||||
"title_field": "employee_name",
|
||||
"track_seen": 0
|
||||
}
|
@ -16,7 +16,7 @@ class SalaryStructure(Document):
|
||||
self.name = make_autoname(self.employee + '/.SST' + '/.#####')
|
||||
|
||||
def validate(self):
|
||||
self.check_existing()
|
||||
self.check_overlap()
|
||||
self.validate_amount()
|
||||
self.validate_employee()
|
||||
self.validate_joining_date()
|
||||
@ -48,43 +48,44 @@ class SalaryStructure(Document):
|
||||
for li in list1:
|
||||
child = self.append(tab_fname, {})
|
||||
if(tab_fname == 'earnings'):
|
||||
child.e_type = cstr(li[0])
|
||||
child.earning_type = cstr(li[0])
|
||||
child.modified_value = 0
|
||||
elif(tab_fname == 'deductions'):
|
||||
child.d_type = cstr(li[0])
|
||||
child.deduction_type = cstr(li[0])
|
||||
child.d_modified_amt = 0
|
||||
|
||||
def make_earn_ded_table(self):
|
||||
self.make_table('Earning Type','earnings','Salary Structure Earning')
|
||||
self.make_table('Deduction Type','deductions', 'Salary Structure Deduction')
|
||||
|
||||
def check_existing(self):
|
||||
ret = self.get_other_active_salary_structure()
|
||||
|
||||
if ret and self.is_active=='Yes':
|
||||
frappe.throw(_("Another Salary Structure {0} is active for employee {1}. Please make its status 'Inactive' to proceed.").format(ret, self.employee))
|
||||
|
||||
def get_other_active_salary_structure(self):
|
||||
ret = frappe.db.sql("""select name from `tabSalary Structure` where is_active = 'Yes'
|
||||
and employee = %s and name!=%s""", (self.employee,self.name))
|
||||
|
||||
return ret[0][0] if ret else None
|
||||
|
||||
def before_test_insert(self):
|
||||
"""Make any existing salary structure for employee inactive."""
|
||||
ret = self.get_other_active_salary_structure()
|
||||
if ret:
|
||||
frappe.db.set_value("Salary Structure", ret, "is_active", "No")
|
||||
def check_overlap(self):
|
||||
existing = frappe.db.sql("""select name from `tabSalary Structure`
|
||||
where employee = %(employee)s and
|
||||
(
|
||||
(%(from_date)s > from_date and %(from_date)s < to_date) or
|
||||
(%(to_date)s > from_date and %(to_date)s < to_date) or
|
||||
(%(from_date)s <= from_date and %(to_date)s >= to_date))
|
||||
and name!=%(name)s
|
||||
and docstatus < 2""",
|
||||
{
|
||||
"employee": self.employee,
|
||||
"from_date": self.from_date,
|
||||
"to_date": self.to_date,
|
||||
"name": self.name or "No Name"
|
||||
}, as_dict=True)
|
||||
|
||||
if existing:
|
||||
frappe.throw(_("Salary structure {0} already exist, more than one salary structure for same period is not allowed").format(existing[0].name))
|
||||
|
||||
def validate_amount(self):
|
||||
if flt(self.net_pay) < 0:
|
||||
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
|
||||
frappe.throw(_("Net pay cannot be negative"))
|
||||
|
||||
def validate_employee(self):
|
||||
old_employee = frappe.db.get_value("Salary Structure", self.name, "employee")
|
||||
if old_employee and self.employee != old_employee:
|
||||
frappe.throw(_("Employee can not be changed"))
|
||||
|
||||
|
||||
def validate_joining_date(self):
|
||||
joining_date = getdate(frappe.db.get_value("Employee", self.employee, "date_of_joining"))
|
||||
if getdate(self.from_date) < joining_date:
|
||||
@ -93,6 +94,7 @@ class SalaryStructure(Document):
|
||||
@frappe.whitelist()
|
||||
def make_salary_slip(source_name, target_doc=None):
|
||||
def postprocess(source, target):
|
||||
target.salary_structure = source.name
|
||||
target.run_method("pull_emp_details")
|
||||
target.run_method("get_leave_details")
|
||||
target.run_method("calculate_net_pay")
|
||||
@ -109,7 +111,7 @@ def make_salary_slip(source_name, target_doc=None):
|
||||
"field_map": [
|
||||
["depend_on_lwp", "d_depends_on_lwp"],
|
||||
["d_modified_amt", "d_amount"],
|
||||
["d_modified_amt", "d_modified_amount"]
|
||||
["d_modified_amt", "deduction_amount"]
|
||||
],
|
||||
"add_if_empty": True
|
||||
},
|
||||
@ -117,8 +119,8 @@ def make_salary_slip(source_name, target_doc=None):
|
||||
"doctype": "Salary Slip Earning",
|
||||
"field_map": [
|
||||
["depend_on_lwp", "e_depends_on_lwp"],
|
||||
["modified_value", "e_modified_amount"],
|
||||
["modified_value", "e_amount"]
|
||||
["modified_value", "e_amount"],
|
||||
["modified_value", "earning_amount"]
|
||||
],
|
||||
"add_if_empty": True
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
"from_date": "2014-02-01",
|
||||
"earnings": [
|
||||
{
|
||||
"e_type": "_Test Basic Salary"
|
||||
"earning_type": "_Test Basic Salary"
|
||||
},
|
||||
{
|
||||
"e_type": "_Test Allowance"
|
||||
"earning_type": "_Test Allowance"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,19 +2,22 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:48",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "d_type",
|
||||
"fieldname": "deduction_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
@ -25,6 +28,7 @@
|
||||
"options": "Deduction Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "200px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
@ -42,6 +46,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
@ -52,6 +57,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -67,6 +73,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reduce Deduction for Leave Without Pay (LWP)",
|
||||
@ -76,6 +83,7 @@
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -87,18 +95,21 @@
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:55.793274",
|
||||
"modified": "2016-06-27 15:36:26.491643",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Structure Deduction",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
"read_only_onload": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:48",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -11,10 +12,11 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "e_type",
|
||||
"fieldname": "earning_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
@ -25,6 +27,7 @@
|
||||
"options": "Earning Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "200px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
@ -42,6 +45,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
@ -52,6 +56,7 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -67,6 +72,7 @@
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reduce Earning for Leave Without Pay (LWP)",
|
||||
@ -76,6 +82,7 @@
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -87,18 +94,22 @@
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:55.830492",
|
||||
"modified": "2016-06-27 15:28:58.884891",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Salary Structure Earning",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
@ -42,12 +42,12 @@ def get_columns(salary_slips):
|
||||
_("Payment Days") + ":Float:120"
|
||||
]
|
||||
|
||||
earning_types = frappe.db.sql_list("""select distinct e_type from `tabSalary Slip Earning`
|
||||
where e_modified_amount != 0 and parent in (%s)""" %
|
||||
earning_types = frappe.db.sql_list("""select distinct earning_type from `tabSalary Slip Earning`
|
||||
where earning_amount != 0 and parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]))
|
||||
|
||||
ded_types = frappe.db.sql_list("""select distinct d_type from `tabSalary Slip Deduction`
|
||||
where d_modified_amount != 0 and parent in (%s)""" %
|
||||
ded_types = frappe.db.sql_list("""select distinct deduction_type from `tabSalary Slip Deduction`
|
||||
where deduction_amount != 0 and parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]))
|
||||
|
||||
columns = columns + [(e + ":Currency:120") for e in earning_types] + \
|
||||
@ -83,25 +83,25 @@ def get_conditions(filters):
|
||||
return conditions, filters
|
||||
|
||||
def get_ss_earning_map(salary_slips):
|
||||
ss_earnings = frappe.db.sql("""select parent, e_type, e_modified_amount
|
||||
ss_earnings = frappe.db.sql("""select parent, earning_type, earning_amount
|
||||
from `tabSalary Slip Earning` where parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
|
||||
|
||||
ss_earning_map = {}
|
||||
for d in ss_earnings:
|
||||
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.e_type, [])
|
||||
ss_earning_map[d.parent][d.e_type] = flt(d.e_modified_amount)
|
||||
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.earning_type, [])
|
||||
ss_earning_map[d.parent][d.earning_type] = flt(d.earning_amount)
|
||||
|
||||
return ss_earning_map
|
||||
|
||||
def get_ss_ded_map(salary_slips):
|
||||
ss_deductions = frappe.db.sql("""select parent, d_type, d_modified_amount
|
||||
ss_deductions = frappe.db.sql("""select parent, deduction_type, deduction_amount
|
||||
from `tabSalary Slip Deduction` where parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
|
||||
|
||||
ss_ded_map = {}
|
||||
for d in ss_deductions:
|
||||
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.d_type, [])
|
||||
ss_ded_map[d.parent][d.d_type] = flt(d.d_modified_amount)
|
||||
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.deduction_type, [])
|
||||
ss_ded_map[d.parent][d.deduction_type] = flt(d.deduction_amount)
|
||||
|
||||
return ss_ded_map
|
@ -16,6 +16,10 @@ frappe.ui.form.on("Production Order", {
|
||||
});
|
||||
erpnext.production_order.set_default_warehouse(frm);
|
||||
}
|
||||
|
||||
// formatter for production order operation
|
||||
frm.set_indicator_formatter('operation',
|
||||
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" })
|
||||
|
||||
erpnext.production_order.set_custom_buttons(frm);
|
||||
erpnext.production_order.setup_company_filter(frm);
|
||||
@ -33,6 +37,15 @@ frappe.ui.form.on("Production Order", {
|
||||
if (frm.doc.docstatus===1) {
|
||||
frm.trigger('show_progress');
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus == 1){
|
||||
frm.add_custom_button(__('Make Timesheet'), function(){
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.manufacturing.doctype.production_order.production_order.make_timesheet",
|
||||
frm: cur_frm
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
show_progress: function(frm) {
|
||||
var bars = [];
|
||||
@ -90,7 +103,7 @@ frappe.ui.form.on("Production Order Operation", {
|
||||
time_in_mins: function(frm, cdt, cdn) {
|
||||
erpnext.production_order.calculate_cost(frm.doc);
|
||||
erpnext.production_order.calculate_total_cost(frm);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
erpnext.production_order = {
|
||||
@ -112,9 +125,9 @@ erpnext.production_order = {
|
||||
|
||||
// opertions
|
||||
if ((doc.operations || []).length) {
|
||||
frm.add_custom_button(__('Time Logs'), function() {
|
||||
frm.add_custom_button(__('Time Sheet'), function() {
|
||||
frappe.route_options = {"production_order": frm.doc.name};
|
||||
frappe.set_route("List", "Time Log");
|
||||
frappe.set_route("List", "Time Sheet");
|
||||
}, __("View"));
|
||||
}
|
||||
|
||||
@ -252,32 +265,6 @@ $.extend(cur_frm.cscript, {
|
||||
qty: function() {
|
||||
frappe.ui.form.trigger("Production Order", 'bom_no')
|
||||
},
|
||||
show_time_logs: function(doc, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
frappe.route_options = {"operation_id": child.name};
|
||||
frappe.set_route("List", "Time Log");
|
||||
},
|
||||
|
||||
make_time_log: function(doc, cdt, cdn){
|
||||
var child = locals[cdt][cdn]
|
||||
frappe.call({
|
||||
method:"erpnext.manufacturing.doctype.production_order.production_order.make_time_log",
|
||||
args: {
|
||||
"name": doc.name,
|
||||
"operation": child.operation,
|
||||
"from_time": child.planned_start_time,
|
||||
"to_time": child.planned_end_time,
|
||||
"project": doc.project,
|
||||
"workstation": child.workstation,
|
||||
"qty": flt(doc.qty) - flt(child.completed_qty),
|
||||
"operation_id": child.name
|
||||
},
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.cscript['Stop Production Order'] = function() {
|
||||
|
@ -1084,13 +1084,14 @@
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-cogs",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-05-11 12:17:29.480533",
|
||||
"modified": "2016-06-23 10:20:22.075476",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Order",
|
||||
@ -1115,6 +1116,26 @@
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
|
@ -4,14 +4,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
import json
|
||||
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError
|
||||
from erpnext.projects.doctype.time_sheet.time_sheet import OverlapError
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
|
||||
@ -158,6 +160,11 @@ class ProductionOrder(Document):
|
||||
|
||||
def before_submit(self):
|
||||
self.set_required_items()
|
||||
self.make_timesheets()
|
||||
|
||||
def before_cancel(self):
|
||||
for data in self.operations:
|
||||
data.time_sheet = None
|
||||
|
||||
def on_submit(self):
|
||||
if not self.wip_warehouse:
|
||||
@ -166,7 +173,6 @@ class ProductionOrder(Document):
|
||||
frappe.throw(_("For Warehouse is required before Submit"))
|
||||
|
||||
self.update_reserved_qty_for_production()
|
||||
self.make_time_logs()
|
||||
self.update_completed_qty_in_material_request()
|
||||
self.update_planned_qty()
|
||||
|
||||
@ -175,7 +181,7 @@ class ProductionOrder(Document):
|
||||
|
||||
frappe.db.set(self,'status', 'Cancelled')
|
||||
self.clear_required_items()
|
||||
self.delete_time_logs()
|
||||
self.delete_time_sheet()
|
||||
self.update_completed_qty_in_material_request()
|
||||
self.update_planned_qty()
|
||||
|
||||
@ -233,8 +239,8 @@ class ProductionOrder(Document):
|
||||
holidays[holiday_list] = holiday_list_days
|
||||
|
||||
return holidays[holiday_list]
|
||||
|
||||
def make_time_logs(self):
|
||||
|
||||
def make_timesheets(self):
|
||||
"""Capacity Planning. Plan time logs based on earliest availablity of workstation after
|
||||
Planned Start Date. Time logs will be created and remain in Draft mode and must be submitted
|
||||
before manufacturing entry can be made."""
|
||||
@ -242,72 +248,80 @@ class ProductionOrder(Document):
|
||||
if not self.operations:
|
||||
return
|
||||
|
||||
time_logs = []
|
||||
time_sheets = []
|
||||
plan_days = frappe.db.get_single_value("Manufacturing Settings", "capacity_planning_for_days") or 30
|
||||
|
||||
time_sheet = make_time_sheet(self.name)
|
||||
workstation_list = []
|
||||
last_workstation_idx = {}
|
||||
|
||||
for i, d in enumerate(self.operations):
|
||||
self.set_operation_start_end_time(i, d)
|
||||
|
||||
time_log = make_time_log(self.name, d.operation, d.planned_start_time, d.planned_end_time,
|
||||
flt(self.qty) - flt(d.completed_qty), self.project, d.workstation, operation_id=d.name)
|
||||
|
||||
if d.workstation:
|
||||
last_workstation_idx[d.workstation] = i # set last row index of workstation
|
||||
self.set_start_end_time_for_workstation(d, workstation_list, last_workstation_idx.get(d.workstation))
|
||||
|
||||
args = self.get_operations_data(d)
|
||||
add_timesheet_detail(time_sheet, args)
|
||||
original_start_time = d.planned_start_time
|
||||
|
||||
# validate operating hours if workstation [not mandatory] is specified
|
||||
self.check_operation_fits_in_working_hours(d)
|
||||
|
||||
original_start_time = time_log.from_time
|
||||
while True:
|
||||
_from_time = time_log.from_time
|
||||
|
||||
try:
|
||||
time_log.save()
|
||||
break
|
||||
except WorkstationHolidayError:
|
||||
time_log.move_to_next_day()
|
||||
except NotInWorkingHoursError:
|
||||
time_log.move_to_next_working_slot()
|
||||
time_sheet.validate_timesheets()
|
||||
except OverlapError:
|
||||
time_log.move_to_next_non_overlapping_slot()
|
||||
time_sheet.move_to_next_non_overlapping_slot(d.idx)
|
||||
|
||||
# reset end time
|
||||
time_log.to_time = get_datetime(time_log.from_time) + relativedelta(minutes=d.time_in_mins)
|
||||
from_time, to_time = self.get_start_end_time(time_sheet, d.name)
|
||||
|
||||
if date_diff(time_log.from_time, original_start_time) > plan_days:
|
||||
frappe.msgprint(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
|
||||
if date_diff(from_time, original_start_time) > plan_days:
|
||||
frappe.throw(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
|
||||
break
|
||||
|
||||
# if time log needs to be moved, make sure that the from time is not the same
|
||||
if _from_time == time_log.from_time:
|
||||
frappe.throw(_("Capacity Planning Error"))
|
||||
d.planned_start_time = from_time
|
||||
d.planned_end_time = to_time
|
||||
d.db_update()
|
||||
|
||||
d.planned_start_time = time_log.from_time
|
||||
d.planned_end_time = time_log.to_time
|
||||
d.db_update()
|
||||
|
||||
if time_log.name:
|
||||
time_logs.append(time_log.name)
|
||||
if time_sheet:
|
||||
time_sheet.save()
|
||||
time_sheets.append(time_sheet.name)
|
||||
|
||||
self.planned_end_date = self.operations[-1].planned_end_time
|
||||
|
||||
if time_logs:
|
||||
if time_sheets:
|
||||
frappe.local.message_log = []
|
||||
frappe.msgprint(_("Time Logs created:") + "\n" + "\n".join(time_logs))
|
||||
frappe.msgprint(_("Time Sheet created:") + "\n" + "\n".join(time_sheets))
|
||||
|
||||
def set_operation_start_end_time(self, i, d):
|
||||
def get_operations_data(self, data):
|
||||
return {
|
||||
'from_time': data.planned_start_time,
|
||||
'hours': data.time_in_mins / 60,
|
||||
'to_time': data.planned_end_time,
|
||||
'project': self.project,
|
||||
'operation': data.operation,
|
||||
'operation_id': data.name,
|
||||
'workstation': data.workstation,
|
||||
'completed_qty': flt(self.qty) - flt(data.completed_qty)
|
||||
}
|
||||
|
||||
def set_start_end_time_for_workstation(self, data, workstation_list, index):
|
||||
"""Set start and end time for given operation. If first operation, set start as
|
||||
`planned_start_date`, else add time diff to end time of earlier operation."""
|
||||
if self.planned_start_date:
|
||||
if i==0:
|
||||
# first operation at planned_start date
|
||||
d.planned_start_time = self.planned_start_date
|
||||
else:
|
||||
d.planned_start_time = get_datetime(self.operations[i-1].planned_end_time)\
|
||||
+ get_mins_between_operations()
|
||||
|
||||
d.planned_end_time = get_datetime(d.planned_start_time) + relativedelta(minutes = d.time_in_mins)
|
||||
if data.workstation not in workstation_list:
|
||||
data.planned_start_time = self.planned_start_date
|
||||
workstation_list.append(data.workstation)
|
||||
else:
|
||||
data.planned_start_time = get_datetime(self.operations[index-1].planned_end_time)\
|
||||
+ get_mins_between_operations()
|
||||
|
||||
if d.planned_start_time == d.planned_end_time:
|
||||
frappe.throw(_("Capacity Planning Error"))
|
||||
data.planned_end_time = get_datetime(data.planned_start_time) + relativedelta(minutes = data.time_in_mins)
|
||||
|
||||
if data.planned_start_time == data.planned_end_time:
|
||||
frappe.throw(_("Capacity Planning Error"))
|
||||
|
||||
def get_start_end_time(self, time_sheet, operation_id):
|
||||
for data in time_sheet.timesheets:
|
||||
if data.operation_id == operation_id:
|
||||
return data.from_time, data.to_time
|
||||
|
||||
def check_operation_fits_in_working_hours(self, d):
|
||||
"""Raises expection if operation is longer than working hours in the given workstation."""
|
||||
@ -336,9 +350,9 @@ class ProductionOrder(Document):
|
||||
self.actual_start_date = None
|
||||
self.actual_end_date = None
|
||||
|
||||
def delete_time_logs(self):
|
||||
for time_log in frappe.get_all("Time Log", ["name"], {"production_order": self.name}):
|
||||
frappe.delete_doc("Time Log", time_log.name)
|
||||
def delete_time_sheet(self):
|
||||
for time_sheet in frappe.get_all("Time Sheet", ["name"], {"production_order": self.name}):
|
||||
frappe.delete_doc("Time Sheet", time_sheet.name)
|
||||
|
||||
def validate_production_item(self):
|
||||
if frappe.db.get_value("Item", self.production_item, "has_variants"):
|
||||
@ -490,22 +504,22 @@ def get_events(start, end, filters=None):
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_time_log(name, operation, from_time=None, to_time=None, qty=None, project=None, workstation=None, operation_id=None):
|
||||
time_log = frappe.new_doc("Time Log")
|
||||
time_log.for_manufacturing = 1
|
||||
time_log.from_time = from_time
|
||||
time_log.to_time = to_time
|
||||
time_log.production_order = name
|
||||
time_log.project = project
|
||||
time_log.operation_id = operation_id
|
||||
time_log.operation = operation
|
||||
time_log.workstation= workstation
|
||||
time_log.activity_type= "Manufacturing"
|
||||
time_log.completed_qty = flt(qty)
|
||||
def make_time_sheet(production_order):
|
||||
time_sheet = frappe.new_doc("Time Sheet")
|
||||
time_sheet.employee = ""
|
||||
time_sheet.production_order = production_order
|
||||
return time_sheet
|
||||
|
||||
if from_time and to_time :
|
||||
time_log.calculate_total_hours()
|
||||
return time_log
|
||||
@frappe.whitelist()
|
||||
def add_timesheet_detail(time_sheet, args):
|
||||
if isinstance(time_sheet, unicode):
|
||||
time_sheet = frappe.get_doc('Time Sheet', time_sheet)
|
||||
|
||||
if isinstance(args, unicode):
|
||||
args = json.loads(args)
|
||||
|
||||
time_sheet.append('timesheets', args)
|
||||
return time_sheet
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_warehouse():
|
||||
@ -514,3 +528,33 @@ def get_default_warehouse():
|
||||
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings",
|
||||
"default_fg_warehouse")
|
||||
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_timesheet(source_name, target_doc=None):
|
||||
def postprocess(source, target):
|
||||
target.production_order = source.name
|
||||
target.naming_series = 'TS-'
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
target.completed_qty = source_parent.qty - source.completed_qty
|
||||
|
||||
doc = get_mapped_doc("Production Order", source_name, {
|
||||
"Production Order": {
|
||||
"doctype": "Time Sheet",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Production Order Operation": {
|
||||
"doctype": "Time Sheet Detail",
|
||||
"field_map": {
|
||||
"name": "operation_id",
|
||||
"operation": "operation",
|
||||
"workstation": "workstation"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: doc.status != 'Completed'
|
||||
}
|
||||
}, target_doc, postprocess)
|
||||
|
||||
return doc
|
||||
|
@ -10,7 +10,6 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_per
|
||||
from erpnext.manufacturing.doctype.production_order.production_order \
|
||||
import make_stock_entry, ItemHasVariantError
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.projects.doctype.time_log.time_log import OverProductionLoggedError
|
||||
from erpnext.stock.utils import get_bin
|
||||
|
||||
class TestProductionOrder(unittest.TestCase):
|
||||
@ -72,25 +71,25 @@ class TestProductionOrder(unittest.TestCase):
|
||||
|
||||
self.assertRaises(StockOverProductionError, s.submit)
|
||||
|
||||
def test_make_time_log(self):
|
||||
from erpnext.projects.doctype.time_log.test_time_log import make_time_log_test_record
|
||||
def test_make_time_sheet(self):
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import make_timesheet
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2",
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.insert()
|
||||
prod_order.submit()
|
||||
|
||||
|
||||
d = prod_order.operations[0]
|
||||
|
||||
d.completed_qty = flt(d.completed_qty)
|
||||
|
||||
time_log = make_time_log_test_record(hours=1, production_order= prod_order.name, operation= d.operation,
|
||||
completed_qty= prod_order.qty - d.completed_qty, operation_id=d.name, for_manufacturing=1, simulate=True)
|
||||
name = frappe.db.get_value('Time Sheet', {'production_order': prod_order.name}, 'name')
|
||||
time_sheet_doc = frappe.get_doc('Time Sheet', name)
|
||||
time_sheet_doc.submit()
|
||||
|
||||
|
||||
self.assertEqual(prod_order.name, time_log.production_order)
|
||||
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(prod_order.name, time_sheet_doc.production_order)
|
||||
self.assertEqual((prod_order.qty - d.completed_qty), sum([d.completed_qty for d in time_sheet_doc.timesheets]))
|
||||
|
||||
manufacturing_settings = frappe.get_doc({
|
||||
"doctype": "Manufacturing Settings",
|
||||
@ -105,8 +104,11 @@ class TestProductionOrder(unittest.TestCase):
|
||||
|
||||
self.assertEqual(prod_order.operations[0].actual_operation_time, 60)
|
||||
self.assertEqual(prod_order.operations[0].actual_operating_cost, 100)
|
||||
|
||||
time_sheet_doc1 = make_timesheet(prod_order.name)
|
||||
self.assertEqual(len(time_sheet_doc1.get('timesheets')), 0)
|
||||
|
||||
time_log.cancel()
|
||||
time_sheet_doc.cancel()
|
||||
|
||||
prod_order.load_from_db()
|
||||
self.assertEqual(prod_order.operations[0].status, "Pending")
|
||||
@ -115,11 +117,6 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertEqual(flt(prod_order.operations[0].actual_operation_time), 0)
|
||||
self.assertEqual(flt(prod_order.operations[0].actual_operating_cost), 0)
|
||||
|
||||
time_log2 = make_time_log_test_record(from_time= add_days(time_log.to_time, 1) ,production_order= prod_order.name, operation= d.operation,
|
||||
completed_qty= 5, operation_id=d.name, for_manufacturing=1, do_not_save=True)
|
||||
|
||||
self.assertRaises(OverProductionLoggedError, time_log2.save)
|
||||
|
||||
def test_planned_operating_cost(self):
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2",
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
|
@ -2,6 +2,7 @@
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2014-10-16 14:35:41.950175",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -16,6 +17,7 @@
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
@ -24,6 +26,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -39,6 +42,7 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Operation",
|
||||
@ -50,6 +54,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -65,6 +70,7 @@
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Operation Description",
|
||||
@ -75,6 +81,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
@ -90,6 +97,7 @@
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
@ -97,6 +105,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -113,6 +122,7 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Completed Qty",
|
||||
@ -121,6 +131,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -137,6 +148,7 @@
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
@ -146,6 +158,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -161,6 +174,7 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Workstation",
|
||||
@ -172,30 +186,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:(doc.docstatus==1)",
|
||||
"fieldname": "show_time_logs",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Show Time Logs",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -211,6 +202,7 @@
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Estimated Time and Cost",
|
||||
@ -219,6 +211,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -234,6 +227,7 @@
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Planned Start Time",
|
||||
@ -242,6 +236,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -257,6 +252,7 @@
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Planned End Time",
|
||||
@ -265,6 +261,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -280,6 +277,7 @@
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
@ -287,6 +285,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -303,6 +302,7 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Operation Time",
|
||||
@ -313,6 +313,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
@ -328,6 +329,7 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Hour Rate",
|
||||
@ -338,6 +340,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -353,6 +356,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Planned Operating Cost",
|
||||
@ -362,6 +366,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -377,6 +382,7 @@
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual Time and Cost",
|
||||
@ -385,6 +391,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -400,6 +407,7 @@
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual Start Time",
|
||||
@ -408,6 +416,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -424,6 +433,7 @@
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual End Time",
|
||||
@ -432,6 +442,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -447,6 +458,7 @@
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
@ -454,6 +466,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -470,6 +483,7 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual Operation Time",
|
||||
@ -478,6 +492,7 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -494,6 +509,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual Operating Cost",
|
||||
@ -503,55 +519,36 @@
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:(doc.docstatus==1 && doc.status!=\"Completed\")",
|
||||
"fieldname": "make_time_log",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Make Time Log",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:52.476364",
|
||||
"modified": "2016-06-24 16:12:08.380635",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Order Operation",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
@ -276,3 +276,8 @@ erpnext.patches.v7_0.re_route #2016-06-27
|
||||
erpnext.patches.v7_0.create_warehouse_nestedset
|
||||
erpnext.patches.v7_0.system_settings_setup_complete
|
||||
erpnext.patches.v7_0.merge_account_type_stock_and_warehouse_to_stock
|
||||
erpnext.patches.v7_0.set_naming_series_for_timesheet
|
||||
erpnext.patches.v7_0.convert_timelogbatch_to_timesheet
|
||||
erpnext.patches.v7_0.convert_timelog_to_timesheet
|
||||
erpnext.patches.v7_0.move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet
|
||||
erpnext.patches.v7_0.remove_doctypes_and_reports
|
||||
|
@ -182,8 +182,8 @@ rename_map = {
|
||||
"Territory": [
|
||||
["target_details", "targets"]
|
||||
],
|
||||
"Time Log Batch": [
|
||||
["time_log_batch_details", "time_logs"]
|
||||
"Time Sheet": [
|
||||
["time_sheet_details", "time_logs"]
|
||||
],
|
||||
"Workstation": [
|
||||
["workstation_operation_hours", "working_hours"]
|
||||
|
26
erpnext/patches/v7_0/convert_timelog_to_timesheet.py
Normal file
26
erpnext/patches/v7_0/convert_timelog_to_timesheet.py
Normal file
@ -0,0 +1,26 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import make_time_sheet, add_timesheet_detail
|
||||
|
||||
def execute():
|
||||
for data in frappe.get_all('Time Log', fields=["*"],
|
||||
filters = [["docstatus", "<", "2"]]):
|
||||
time_sheet = make_time_sheet(data.production_order)
|
||||
args = get_timesheet_data(data)
|
||||
add_timesheet_detail(time_sheet, args)
|
||||
time_sheet.docstatus = data.docstatus
|
||||
time_sheet.company = frappe.db.get_single_value('Global Defaults', 'default_company')
|
||||
time_sheet.save(ignore_permissions=True)
|
||||
|
||||
def get_timesheet_data(data):
|
||||
return {
|
||||
'from_time': data.from_time,
|
||||
'hours': data.hours,
|
||||
'to_time': data.to_time,
|
||||
'project': data.project,
|
||||
'activity_type': data.activity_type or "Planning",
|
||||
'operation': data.operation,
|
||||
'operation_id': data.operation_id,
|
||||
'workstation': data.workstation,
|
||||
'completed_qty': data.completed_qty
|
||||
}
|
32
erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py
Normal file
32
erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py
Normal file
@ -0,0 +1,32 @@
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import add_timesheet_detail
|
||||
|
||||
def execute():
|
||||
for tlb in frappe.get_all('Time Log Batch', fields=["*"],
|
||||
filters = [["docstatus", "<", "2"]]):
|
||||
time_sheet = frappe.new_doc('Time Sheet')
|
||||
time_sheet.employee= ""
|
||||
time_sheet.company = frappe.db.get_single_value('Global Defaults', 'default_company')
|
||||
time_sheet.sales_invoice = tlb.sales_invoice
|
||||
for data in tlb.time_logs:
|
||||
args = get_timesheet_data(data)
|
||||
add_timesheet_detail(time_sheet, args)
|
||||
|
||||
time_sheet.docstatus = tlb.docstatus
|
||||
time_sheet.save(ignore_permissions=True)
|
||||
|
||||
def get_timesheet_data(data):
|
||||
time_log = frappe.get_doc('Time Log', data.time_log)
|
||||
|
||||
return {
|
||||
'from_time': time_log.from_time,
|
||||
'hours': time_log.hours,
|
||||
'to_time': time_log.to_time,
|
||||
'project': time_log.project,
|
||||
'activity_type': time_log.activity_type,
|
||||
'operation': time_log.operation,
|
||||
'operation_id': time_log.operation_id,
|
||||
'workstation': time_log.workstation,
|
||||
'completed_qty': time_log.completed_qty
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import add_timesheet_detail
|
||||
|
||||
def execute():
|
||||
for si in frappe.db.sql(""" select sales_invoice as name from `tabTime Sheet`
|
||||
where sales_invoice is not null and docstatus < 2""", as_dict=True):
|
||||
si_doc = frappe.get_doc('Sales Invoice', si.name)
|
||||
for item in si_doc.items:
|
||||
if item.time_log_batch:
|
||||
ts = si_doc.append('timesheets',{})
|
||||
ts.time_sheet = item.time_log_batch
|
||||
ts.billing_amount = frappe.db.get_value('Time Log Batch', item.time_log_batch, 'total_billing_amount')
|
||||
si_doc.ignore_validate_update_after_submit = True
|
||||
si_doc.update_time_sheet(ts.time_sheet)
|
||||
si_doc.save()
|
9
erpnext/patches/v7_0/remove_doctypes_and_reports.py
Normal file
9
erpnext/patches/v7_0/remove_doctypes_and_reports.py
Normal file
@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for doctype in ['Time Log Batch', 'Time Log Batch Detail', 'Time Log']:
|
||||
frappe.delete_doc('DocType', doctype)
|
||||
|
||||
report = "Daily Time Log Summary"
|
||||
if frappe.db.exists("Report", report):
|
||||
frappe.delete_doc('Report', report)
|
14
erpnext/patches/v7_0/set_naming_series_for_timesheet.py
Normal file
14
erpnext/patches/v7_0/set_naming_series_for_timesheet.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Time Sheet')
|
||||
frappe.reload_doctype('Time Sheet Detail')
|
||||
|
||||
make_property_setter('Time Sheet', "naming_series", "options", 'TS-', "Text")
|
||||
make_property_setter('Time Sheet', "naming_series", "default", 'TS-', "Text")
|
@ -67,7 +67,7 @@ frappe.ui.form.on("Project", {
|
||||
},
|
||||
show_dashboard: function(frm) {
|
||||
frm.dashboard.show_heatmap = true;
|
||||
frm.dashboard.heatmap_message = __('This is based on the Time Logs created against this project');
|
||||
frm.dashboard.heatmap_message = __('This is based on the Time Sheet created against this project');
|
||||
frm.dashboard.show_dashboard();
|
||||
|
||||
if(frm.doc.__onload.activity_summary.length) {
|
||||
@ -82,9 +82,9 @@ frappe.ui.form.on("Project", {
|
||||
sum: sum
|
||||
}));
|
||||
|
||||
section.on('click', '.time-log-link', function() {
|
||||
section.on('click', '.time-sheet-link', function() {
|
||||
var activity_type = $(this).attr('data-activity_type');
|
||||
frappe.set_route('List', 'Time Log',
|
||||
frappe.set_route('List', 'Time Sheet',
|
||||
{'activity_type': activity_type, 'project': frm.doc.name});
|
||||
});
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class Project(Document):
|
||||
|
||||
self.set_onload('links', self.meta.get_links_setup())
|
||||
self.set_onload('activity_summary', frappe.db.sql('''select activity_type, sum(hours) as total_hours
|
||||
from `tabTime Log` where project=%s group by activity_type order by total_hours desc''', self.name, as_dict=True))
|
||||
from `tabTime Sheet Detail` where project=%s group by activity_type order by total_hours desc''', self.name, as_dict=True))
|
||||
|
||||
def __setup__(self):
|
||||
self.onload()
|
||||
@ -95,13 +95,13 @@ class Project(Document):
|
||||
self.percent_complete = flt(flt(completed) / total * 100, 2)
|
||||
|
||||
def update_costing(self):
|
||||
from_time_log = frappe.db.sql("""select
|
||||
from_time_sheet = frappe.db.sql("""select
|
||||
sum(costing_amount) as costing_amount,
|
||||
sum(billing_amount) as billing_amount,
|
||||
min(from_time) as start_date,
|
||||
max(to_time) as end_date,
|
||||
sum(hours) as time
|
||||
from `tabTime Log` where project = %s and docstatus = 1""", self.name, as_dict=1)[0]
|
||||
from `tabTime Sheet Detail` where project = %s and docstatus = 1""", self.name, as_dict=1)[0]
|
||||
|
||||
from_expense_claim = frappe.db.sql("""select
|
||||
sum(total_sanctioned_amount) as total_sanctioned_amount
|
||||
@ -109,12 +109,12 @@ class Project(Document):
|
||||
and docstatus = 1""",
|
||||
self.name, as_dict=1)[0]
|
||||
|
||||
self.actual_start_date = from_time_log.start_date
|
||||
self.actual_end_date = from_time_log.end_date
|
||||
self.actual_start_date = from_time_sheet.start_date
|
||||
self.actual_end_date = from_time_sheet.end_date
|
||||
|
||||
self.total_costing_amount = from_time_log.costing_amount
|
||||
self.total_billing_amount = from_time_log.billing_amount
|
||||
self.actual_time = from_time_log.time
|
||||
self.total_costing_amount = from_time_sheet.costing_amount
|
||||
self.total_billing_amount = from_time_sheet.billing_amount
|
||||
self.actual_time = from_time_sheet.time
|
||||
|
||||
self.total_expense_claim = from_expense_claim.total_sanctioned_amount
|
||||
|
||||
@ -162,7 +162,7 @@ def get_dashboard_data(name):
|
||||
def get_timeline_data(name):
|
||||
'''Return timeline for attendance'''
|
||||
return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*)
|
||||
from `tabTime Log` where project=%s
|
||||
from `tabTime Sheet Detail` where project=%s
|
||||
and from_time > date_sub(curdate(), interval 1 year)
|
||||
and docstatus < 2
|
||||
group by date(from_time)''', name))
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% for d in data %}
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<a class="small time-log-link" data-activity_type="{{ d.activity_type || "" }}">
|
||||
<a class="small time-sheet-link" data-activity_type="{{ d.activity_type || "" }}">
|
||||
{{ d.activity_type || __("Unknown") }}</a>
|
||||
</div>
|
||||
<div class="col-xs-8">
|
||||
|
@ -5,7 +5,7 @@ links = {
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Project'),
|
||||
'items': ['Task', 'Time Log', 'Expense Claim', 'Issue']
|
||||
'items': ['Task', 'Time Sheet', 'Expense Claim', 'Issue']
|
||||
},
|
||||
{
|
||||
'label': _('Material'),
|
||||
|
@ -16,10 +16,10 @@ frappe.ui.form.on("Task", {
|
||||
|
||||
|
||||
if(!doc.__islocal) {
|
||||
if(frappe.model.can_read("Time Log")) {
|
||||
frm.add_custom_button(__("Time Logs"), function() {
|
||||
if(frappe.model.can_read("Time Sheet")) {
|
||||
frm.add_custom_button(__("Time Sheet"), function() {
|
||||
frappe.route_options = {"project": doc.project, "task": doc.name}
|
||||
frappe.set_route("List", "Time Log");
|
||||
frappe.set_route("List", "Time Sheet");
|
||||
}, __("View"), true);
|
||||
}
|
||||
if(frappe.model.can_read("Expense Claim")) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "TASK.#####",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-29 19:25:50",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
@ -413,7 +414,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual Start Date (via Time Logs)",
|
||||
"label": "Actual Start Date (via Time Sheet)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "act_start_date",
|
||||
@ -491,7 +492,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Actual End Date (via Time Logs)",
|
||||
"label": "Actual End Date (via Time Sheet)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "act_end_date",
|
||||
@ -541,7 +542,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Total Costing Amount (via Time Logs)",
|
||||
"label": "Total Costing Amount (via Time Sheet)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "actual_budget",
|
||||
@ -618,7 +619,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Total Billing Amount (via Time Logs)",
|
||||
"label": "Total Billing Amount (via Time Sheet)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -763,6 +764,7 @@
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-check",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
@ -770,7 +772,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-03-29 01:01:50.074252",
|
||||
"modified": "2016-06-20 17:31:00.798534",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task",
|
||||
@ -797,6 +799,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "subject",
|
||||
|
@ -58,7 +58,7 @@ class Task(Document):
|
||||
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 task = %s and docstatus=1"""
|
||||
sum(hours) as time from `tabTime Sheet Detail` where task = %s and docstatus=1"""
|
||||
,self.name, as_dict=1)[0]
|
||||
if self.status == "Open":
|
||||
self.status = "Working"
|
||||
|
@ -124,29 +124,6 @@ class TestTask(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||
|
||||
time_log = frappe.new_doc('Time Log')
|
||||
time_log.update({
|
||||
"from_time": "2015-1-1",
|
||||
"to_time": "2015-1-20",
|
||||
"task": task1.name
|
||||
})
|
||||
time_log.submit()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
|
||||
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||
|
||||
time_log.cancel()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
|
||||
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||
|
||||
|
||||
def test_close_assignment(self):
|
||||
task = frappe.new_doc("Task")
|
||||
task.subject = "Test Close Assignment"
|
||||
|
@ -1 +0,0 @@
|
||||
Log of activity done by a user in a particular period.
|
@ -1 +0,0 @@
|
||||
[]
|
@ -1,182 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
import datetime
|
||||
from frappe.utils import now_datetime, now
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError, NotSubmittedError, NegativeHoursError
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record
|
||||
|
||||
|
||||
class TestTimeLog(unittest.TestCase):
|
||||
def test_duplication(self):
|
||||
tl1 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002", simulate= True)
|
||||
|
||||
tl2 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002",
|
||||
from_time= tl1.from_time, to_time= tl1.to_time, do_not_save= 1)
|
||||
|
||||
self.assertRaises(OverlapError, tl2.insert)
|
||||
|
||||
tl3 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002",
|
||||
from_time= tl1.from_time - datetime.timedelta(hours=1),
|
||||
to_time= tl1.to_time + datetime.timedelta(hours=1), do_not_save= 1)
|
||||
|
||||
self.assertRaises(OverlapError, tl3.insert)
|
||||
|
||||
tl4 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002",
|
||||
from_time= tl1.from_time + datetime.timedelta(minutes=20),
|
||||
to_time= tl1.to_time + datetime.timedelta(minutes=30), do_not_save= 1)
|
||||
|
||||
self.assertRaises(OverlapError, tl4.insert)
|
||||
|
||||
make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002",
|
||||
from_time= tl1.to_time,
|
||||
to_time= tl1.to_time + datetime.timedelta(hours=1))
|
||||
|
||||
def test_production_order_status(self):
|
||||
prod_order = make_prod_order_test_record(item= "_Test FG Item 2", qty= 1, do_not_submit= True)
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.save()
|
||||
|
||||
time_log = make_time_log_test_record(for_manufacturing= 1, production_order= prod_order.name, qty= 1,
|
||||
employee= "_T-Employee-0003", do_not_save= True, simulate=1)
|
||||
|
||||
self.assertRaises(NotSubmittedError, time_log.save)
|
||||
|
||||
def test_time_log_on_holiday(self):
|
||||
prod_order = make_prod_order_test_record(item= "_Test FG Item 2", qty= 1,
|
||||
planned_start_date= now(), do_not_save= True)
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.save()
|
||||
prod_order.submit()
|
||||
|
||||
time_log = make_time_log_test_record(from_time= "2013-02-01 10:00:00", to_time= "2013-02-01 20:00:00",
|
||||
for_manufacturing= 1, production_order= prod_order.name, qty= 1,
|
||||
operation= prod_order.operations[0].operation, operation_id= prod_order.operations[0].name,
|
||||
workstation= "_Test Workstation 1", do_not_save= True)
|
||||
|
||||
self.assertRaises(WorkstationHolidayError , time_log.save)
|
||||
|
||||
time_log.update({
|
||||
"from_time": "2013-02-02 09:00:00",
|
||||
"to_time": "2013-02-02 20:00:00"
|
||||
})
|
||||
self.assertRaises(NotInWorkingHoursError , time_log.save)
|
||||
|
||||
time_log.from_time= "2013-02-02 10:30:00"
|
||||
time_log.save()
|
||||
time_log.submit()
|
||||
time_log.cancel()
|
||||
|
||||
def test_negative_hours(self):
|
||||
time_log = make_time_log_test_record(to_time= now_datetime() + datetime.timedelta(minutes=-1),
|
||||
employee="_T-Employee-0006",do_not_save= True)
|
||||
self.assertRaises(NegativeHoursError, time_log.save)
|
||||
|
||||
def test_default_activity_cost(self):
|
||||
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
|
||||
activity_type.billing_rate = 20
|
||||
activity_type.costing_rate = 15
|
||||
activity_type.save()
|
||||
|
||||
project = "_Test Project for Activity Type"
|
||||
|
||||
frappe.db.sql("delete from `tabTime Log` where project=%s or employee='_T-Employee-0002'", project)
|
||||
frappe.delete_doc("Project", project)
|
||||
project = frappe.get_doc({"doctype": "Project", "project_name": project}).insert()
|
||||
|
||||
make_time_log_test_record(employee="_T-Employee-0002", hours=2,
|
||||
activity_type = "_Test Activity Type", project = project.name)
|
||||
|
||||
project = frappe.get_doc("Project", project.name)
|
||||
self.assertTrue(project.total_costing_amount, 30)
|
||||
self.assertTrue(project.total_billing_amount, 40)
|
||||
|
||||
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 `tabTime Log` where name = "_Test Project 1" """)
|
||||
|
||||
if not frappe.db.exists('Activity Cost', {"activity_type": "_Test Activity Type"}):
|
||||
activity_cost = frappe.get_doc({
|
||||
"doctype": "Activity Cost",
|
||||
"employee": "_T-Employee-0002",
|
||||
"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 = make_time_log_test_record(employee="_T-Employee-0002", hours=2,
|
||||
task=task_name, simulate=1)
|
||||
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)
|
||||
|
||||
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 = make_time_log_test_record(employee="_T-Employee-0002",
|
||||
hours=2, task= task_name, from_time = now_datetime() + datetime.timedelta(hours= 3), simulate=1)
|
||||
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)
|
||||
time_log.cancel()
|
||||
|
||||
test_ignore = ["Time Log Batch", "Sales Invoice"]
|
||||
|
||||
def make_time_log_test_record(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
time_log = frappe.new_doc("Time Log")
|
||||
|
||||
time_log.from_time = args.from_time or now_datetime()
|
||||
time_log.hours = args.hours or 1
|
||||
time_log.to_time = args.to_time or time_log.from_time + datetime.timedelta(hours= time_log.hours)
|
||||
|
||||
time_log.project = args.project
|
||||
time_log.task = args.task
|
||||
time_log.for_manufacturing = args.for_manufacturing
|
||||
time_log.production_order = args.production_order
|
||||
time_log.operation = args.operation
|
||||
time_log.operation_id = args.operation_id
|
||||
time_log.workstation = args.workstation
|
||||
time_log.completed_qty = args.completed_qty
|
||||
time_log.activity_type = args.activity_type or "_Test Activity Type"
|
||||
time_log.billable = args.billable or 1
|
||||
time_log.employee = args.employee
|
||||
time_log.user = args.user
|
||||
|
||||
if not args.do_not_save:
|
||||
if args.simulate:
|
||||
while True:
|
||||
try:
|
||||
time_log.save()
|
||||
break
|
||||
except OverlapError:
|
||||
time_log.from_time = time_log.from_time + datetime.timedelta(minutes=10)
|
||||
time_log.to_time = time_log.from_time + datetime.timedelta(hours= time_log.hours)
|
||||
else:
|
||||
time_log.save()
|
||||
if not args.do_not_submit:
|
||||
time_log.submit()
|
||||
|
||||
return time_log
|
@ -1,105 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Time Log", {
|
||||
onload: function(frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
if (frm.doc.for_manufacturing) {
|
||||
frappe.ui.form.trigger("Time Log", "production_order");
|
||||
}
|
||||
if (frm.doc.from_time && frm.doc.to_time) {
|
||||
frappe.ui.form.trigger("Time Log", "to_time");
|
||||
}
|
||||
}
|
||||
frm.set_query('task', function() {
|
||||
return {
|
||||
filters:{
|
||||
'project': frm.doc.project
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
// set default user if created
|
||||
if (frm.doc.__islocal && !frm.doc.user) {
|
||||
frm.set_value("user", user);
|
||||
}
|
||||
if (frm.doc.status==='In Progress' && !frm.is_new()) {
|
||||
frm.add_custom_button(__('Finish'), function() {
|
||||
frappe.prompt({
|
||||
fieldtype: 'Datetime',
|
||||
fieldname: 'to_time',
|
||||
label: __('End Time'),
|
||||
'default': dateutil.now_datetime()
|
||||
}, function(value) {
|
||||
frm.set_value('to_time', value.to_time);
|
||||
frm.save();
|
||||
});
|
||||
}).addClass('btn-primary');
|
||||
}
|
||||
|
||||
|
||||
frm.toggle_reqd("activity_type", !frm.doc.for_manufacturing);
|
||||
},
|
||||
hours: function(frm) {
|
||||
if(!frm.doc.from_time) {
|
||||
frm.set_value("from_time", frappe.datetime.now_datetime());
|
||||
}
|
||||
var d = moment(frm.doc.from_time);
|
||||
d.add(frm.doc.hours, "hours");
|
||||
frm._setting_hours = true;
|
||||
frm.set_value("to_time", d.format(moment.defaultDatetimeFormat));
|
||||
frm._setting_hours = false;
|
||||
|
||||
frm.trigger('calculate_cost');
|
||||
},
|
||||
before_save: function(frm) {
|
||||
frm.doc.production_order && frappe.model.remove_from_locals("Production Order",
|
||||
frm.doc.production_order);
|
||||
},
|
||||
to_time: function(frm) {
|
||||
if(frm._setting_hours) return;
|
||||
frm.set_value("hours", moment(cur_frm.doc.to_time).diff(moment(cur_frm.doc.from_time),
|
||||
"seconds") / 3600);
|
||||
},
|
||||
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) + frm.doc.additional_cost);
|
||||
}
|
||||
},
|
||||
additional_cost: function(frm) {
|
||||
frm.trigger('calculate_cost');
|
||||
},
|
||||
activity_type: function(frm) {
|
||||
if (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);
|
||||
frm.trigger('calculate_cost');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
employee: function(frm) {
|
||||
frm.trigger('activity_type');
|
||||
},
|
||||
billable: function(frm) {
|
||||
if (frm.doc.billable==1) {
|
||||
frm.trigger('calculate_cost');
|
||||
}
|
||||
else {
|
||||
frm.set_value("billing_amount", 0);
|
||||
frm.set_value("additional_cost", 0);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
@ -1,296 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, get_datetime, get_time, getdate
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class OverProductionLoggedError(frappe.ValidationError): pass
|
||||
class NotSubmittedError(frappe.ValidationError): pass
|
||||
class NegativeHoursError(frappe.ValidationError): pass
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TimeLog(Document):
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.set_title()
|
||||
self.validate_overlap()
|
||||
self.validate_timings()
|
||||
self.calculate_total_hours()
|
||||
self.validate_time_log_for()
|
||||
self.check_workstation_timings()
|
||||
self.validate_production_order()
|
||||
self.validate_manufacturing()
|
||||
self.set_project_if_missing()
|
||||
self.update_cost()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_production_order()
|
||||
self.update_task_and_project()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_production_order()
|
||||
self.update_task_and_project()
|
||||
|
||||
def before_update_after_submit(self):
|
||||
self.set_status()
|
||||
|
||||
def before_cancel(self):
|
||||
self.set_status()
|
||||
|
||||
def set_status(self):
|
||||
self.status = {
|
||||
0: "To Submit",
|
||||
1: "Submitted",
|
||||
2: "Cancelled"
|
||||
}[self.docstatus or 0]
|
||||
|
||||
if not self.to_time:
|
||||
self.status = 'In Progress'
|
||||
|
||||
if self.time_log_batch:
|
||||
self.status= "Batched for Billing"
|
||||
|
||||
if self.sales_invoice:
|
||||
self.status= "Billed"
|
||||
|
||||
def set_title(self):
|
||||
"""Set default title for the Time Log"""
|
||||
if self.title:
|
||||
return
|
||||
|
||||
from frappe.utils import get_fullname
|
||||
if self.production_order:
|
||||
self.title = _("{0} for {1}").format(self.operation, self.production_order)
|
||||
elif self.activity_type and (self.task or self.project):
|
||||
self.title = _("{0} for {1}").format(self.activity_type, self.task or self.project)
|
||||
else:
|
||||
self.title = self.task or self.project or get_fullname(frappe.session.user)
|
||||
|
||||
def validate_overlap(self):
|
||||
"""Checks if 'Time Log' entries overlap for a user, workstation. """
|
||||
self.validate_overlap_for("user")
|
||||
self.validate_overlap_for("employee")
|
||||
self.validate_overlap_for("workstation")
|
||||
|
||||
def validate_overlap_for(self, fieldname):
|
||||
existing = self.get_overlap_for(fieldname)
|
||||
if existing:
|
||||
frappe.throw(_("This Time Log conflicts with {0} for {1} {2}").format(existing.name,
|
||||
self.meta.get_label(fieldname), self.get(fieldname)), OverlapError)
|
||||
|
||||
def get_overlap_for(self, fieldname):
|
||||
if not self.get(fieldname):
|
||||
return
|
||||
|
||||
existing = frappe.db.sql("""select name, from_time, to_time from `tabTime Log`
|
||||
where `{0}`=%(val)s and
|
||||
(
|
||||
(%(from_time)s > from_time and %(from_time)s < to_time) or
|
||||
(%(to_time)s > from_time and %(to_time)s < to_time) or
|
||||
(%(from_time)s <= from_time and %(to_time)s >= to_time))
|
||||
and name!=%(name)s
|
||||
and docstatus < 2""".format(fieldname),
|
||||
{
|
||||
"val": self.get(fieldname),
|
||||
"from_time": self.from_time,
|
||||
"to_time": self.to_time,
|
||||
"name": self.name or "No Name"
|
||||
}, as_dict=True)
|
||||
|
||||
return existing[0] if existing else None
|
||||
|
||||
def validate_timings(self):
|
||||
if self.to_time and self.from_time and get_datetime(self.to_time) <= get_datetime(self.from_time):
|
||||
frappe.throw(_("To Time must be greater than From Time"), NegativeHoursError)
|
||||
|
||||
def calculate_total_hours(self):
|
||||
if self.to_time and self.from_time:
|
||||
from frappe.utils import time_diff_in_seconds
|
||||
self.hours = flt(time_diff_in_seconds(self.to_time, self.from_time)) / 3600
|
||||
|
||||
def set_project_if_missing(self):
|
||||
"""Set project if task is set"""
|
||||
if self.task and not self.project:
|
||||
self.project = frappe.db.get_value("Task", self.task, "project")
|
||||
|
||||
def validate_time_log_for(self):
|
||||
if not self.for_manufacturing:
|
||||
for fld in ["production_order", "operation", "workstation", "completed_qty"]:
|
||||
self.set(fld, None)
|
||||
else:
|
||||
self.activity_type=None
|
||||
|
||||
def check_workstation_timings(self):
|
||||
"""Checks if **Time Log** is between operating hours of the **Workstation**."""
|
||||
if self.workstation and self.from_time and self.to_time:
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
|
||||
check_if_within_operating_hours(self.workstation, self.operation, self.from_time, self.to_time)
|
||||
|
||||
def validate_production_order(self):
|
||||
"""Throws 'NotSubmittedError' if **production order** is not submitted. """
|
||||
if self.production_order:
|
||||
if frappe.db.get_value("Production Order", self.production_order, "docstatus") != 1 :
|
||||
frappe.throw(_("You can make a time log only against a submitted production order"), NotSubmittedError)
|
||||
|
||||
def update_production_order(self):
|
||||
"""Updates `start_date`, `end_date`, `status` for operation in Production Order."""
|
||||
|
||||
if self.production_order and self.for_manufacturing:
|
||||
if not self.operation_id:
|
||||
frappe.throw(_("Operation ID not set"))
|
||||
|
||||
dates = self.get_operation_start_end_time()
|
||||
summary = self.get_time_log_summary()
|
||||
|
||||
pro = frappe.get_doc("Production Order", self.production_order)
|
||||
for o in pro.operations:
|
||||
if o.name == self.operation_id:
|
||||
o.actual_start_time = dates.start_date
|
||||
o.actual_end_time = dates.end_date
|
||||
o.completed_qty = summary.completed_qty
|
||||
o.actual_operation_time = summary.mins
|
||||
break
|
||||
|
||||
|
||||
pro.flags.ignore_validate_update_after_submit = True
|
||||
pro.update_operation_status()
|
||||
pro.calculate_operating_cost()
|
||||
pro.set_actual_dates()
|
||||
pro.save()
|
||||
|
||||
def get_operation_start_end_time(self):
|
||||
"""Returns Min From and Max To Dates of Time Logs against a specific Operation. """
|
||||
return frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date from `tabTime Log`
|
||||
where production_order = %s and operation = %s and docstatus=1""",
|
||||
(self.production_order, self.operation), as_dict=1)[0]
|
||||
|
||||
def move_to_next_day(self):
|
||||
"""Move start and end time one day forward"""
|
||||
self.from_time = get_datetime(self.from_time) + relativedelta(day=1)
|
||||
|
||||
def move_to_next_working_slot(self):
|
||||
"""Move to next working slot from workstation"""
|
||||
workstation = frappe.get_doc("Workstation", self.workstation)
|
||||
slot_found = False
|
||||
for working_hour in workstation.working_hours:
|
||||
if get_datetime(self.from_time).time() < get_time(working_hour.start_time):
|
||||
self.from_time = getdate(self.from_time).strftime("%Y-%m-%d") + " " + working_hour.start_time
|
||||
slot_found = True
|
||||
break
|
||||
|
||||
if not slot_found:
|
||||
# later than last time
|
||||
self.from_time = getdate(self.from_time).strftime("%Y-%m-%d") + " " + workstation.working_hours[0].start_time
|
||||
self.move_to_next_day()
|
||||
|
||||
def move_to_next_non_overlapping_slot(self):
|
||||
"""If in overlap, set start as the end point of the overlapping time log"""
|
||||
overlapping = self.get_overlap_for("workstation") \
|
||||
or self.get_overlap_for("employee") \
|
||||
or self.get_overlap_for("user")
|
||||
|
||||
if not overlapping:
|
||||
frappe.throw(_("Logical error: Must find overlapping"))
|
||||
|
||||
self.from_time = get_datetime(overlapping.to_time) + get_mins_between_operations()
|
||||
|
||||
def get_time_log_summary(self):
|
||||
"""Returns 'Actual Operating Time'. """
|
||||
return frappe.db.sql("""select
|
||||
sum(hours*60) as mins, sum(completed_qty) as completed_qty
|
||||
from `tabTime Log`
|
||||
where production_order = %s and operation_id = %s and docstatus=1""",
|
||||
(self.production_order, self.operation_id), as_dict=1)[0]
|
||||
|
||||
def validate_manufacturing(self):
|
||||
if self.for_manufacturing:
|
||||
if not self.production_order:
|
||||
frappe.throw(_("Production Order is Mandatory"))
|
||||
if not self.completed_qty:
|
||||
self.completed_qty = 0
|
||||
|
||||
production_order = frappe.get_doc("Production Order", self.production_order)
|
||||
pending_qty = flt(production_order.qty) - flt(production_order.produced_qty)
|
||||
if flt(self.completed_qty) > pending_qty:
|
||||
frappe.throw(_("Completed Qty cannot be more than {0} for operation {1}").format(pending_qty, self.operation),
|
||||
OverProductionLoggedError)
|
||||
|
||||
else:
|
||||
self.production_order = None
|
||||
self.operation = None
|
||||
self.quantity = None
|
||||
|
||||
def update_cost(self):
|
||||
rate = get_activity_cost(self.employee, self.activity_type)
|
||||
if rate:
|
||||
self.costing_rate = flt(rate.get('costing_rate'))
|
||||
self.billing_rate = flt(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
|
||||
|
||||
if self.additional_cost and self.billable:
|
||||
self.billing_amount += self.additional_cost
|
||||
|
||||
def update_task_and_project(self):
|
||||
"""Update costing rate in Task or Project if either is set"""
|
||||
|
||||
if self.task:
|
||||
task = frappe.get_doc("Task", self.task)
|
||||
task.update_time_and_costing()
|
||||
task.save()
|
||||
|
||||
elif self.project:
|
||||
frappe.get_doc("Project", self.project).update_project()
|
||||
|
||||
def has_webform_permission(doc):
|
||||
project_user = frappe.db.get_value("Project User", {"parent": doc.project, "user":frappe.session.user} , "user")
|
||||
if project_user:
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
"""Returns events for Gantt / Calendar view rendering.
|
||||
|
||||
:param start: Start date-time.
|
||||
:param end: End date-time.
|
||||
:param filters: Filters like workstation, project etc.
|
||||
"""
|
||||
from frappe.desk.calendar import get_event_conditions
|
||||
conditions = get_event_conditions("Time Log", filters)
|
||||
|
||||
data = frappe.db.sql("""select name, from_time, to_time,
|
||||
activity_type, task, project, production_order, workstation from `tabTime Log`
|
||||
where docstatus < 2 and ( from_time between %(start)s and %(end)s or to_time between %(start)s and %(end)s )
|
||||
{conditions}""".format(conditions=conditions), {
|
||||
"start": start,
|
||||
"end": end
|
||||
}, as_dict=True, update={"allDay": 0})
|
||||
|
||||
for d in data:
|
||||
d.title = d.name + ": " + (d.activity_type or d.production_order or "")
|
||||
if d.task:
|
||||
d.title += " for Task: " + d.task
|
||||
if d.project:
|
||||
d.title += " for Project: " + d.project
|
||||
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_activity_cost(employee=None, activity_type=None):
|
||||
rate = frappe.db.get_values("Activity Cost", {"employee": employee,
|
||||
"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True)
|
||||
if not rate:
|
||||
rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type},
|
||||
["costing_rate", "billing_rate"], as_dict=True)
|
||||
|
||||
return rate[0] if rate else {}
|
@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.views.calendar["Time Log"] = {
|
||||
field_map: {
|
||||
"start": "from_time",
|
||||
"end": "to_time",
|
||||
"id": "name",
|
||||
"title": "title",
|
||||
"allDay": "allDay"
|
||||
},
|
||||
gantt: true,
|
||||
gantt_scale: "hours",
|
||||
filters: [
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "workstation",
|
||||
"options": "Workstation",
|
||||
"label": __("Workstation")
|
||||
},
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "employee",
|
||||
"options": "Employee",
|
||||
"label": __("Employee")
|
||||
},
|
||||
],
|
||||
get_events_method: "erpnext.projects.doctype.time_log.time_log.get_events"
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// render
|
||||
frappe.listview_settings['Time Log'] = {
|
||||
add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "for_manufacturing", "billing_amount"],
|
||||
|
||||
has_indicator_for_draft: true,
|
||||
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status== "Draft") {
|
||||
return [__("Draft"), "red", "status,=,Draft"]
|
||||
} else if (doc.status== "In Progress") {
|
||||
return [__("In Progress"), "orange", "status,=,In Progress"]
|
||||
} else if (doc.status== "Batched for Billing") {
|
||||
return [__("Batched for Billing"), "darkgrey", "status,=,Batched for Billing"]
|
||||
} else if (doc.status== "Billed") {
|
||||
return [__("Billed"), "green", "status,=,Billed"]
|
||||
} else if (doc.status== "Submitted" && doc.billable) {
|
||||
return [__("Billable"), "orange", "billable,=,1"]
|
||||
}
|
||||
},
|
||||
|
||||
selectable: true,
|
||||
onload: function(me) {
|
||||
me.page.add_menu_item(__("Make Time Log Batch"), function() {
|
||||
var selected = me.get_checked_items() || [];
|
||||
|
||||
if(!selected.length) {
|
||||
msgprint(__("Please select Time Logs."));
|
||||
return;
|
||||
}
|
||||
|
||||
// select only billable time logs
|
||||
for(var i in selected) {
|
||||
var d = selected[i];
|
||||
if(d.status=="Batched for Billing") {
|
||||
msgprint(__("Time Log has been Batched for Billing") + ": " + d.name + " - " + d.title);
|
||||
return;
|
||||
}
|
||||
if(d.status=="Billed") {
|
||||
msgprint(__("Time Log has been Billed") + ": " + d.name + " - " + d.title);
|
||||
return;
|
||||
}
|
||||
if(d.status!="Submitted") {
|
||||
msgprint(__("Time Log Status must be Submitted.") + ": " + d.name + " - " + d.title);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// make batch
|
||||
frappe.model.with_doctype("Time Log Batch", function() {
|
||||
var tlb = frappe.model.get_new_doc("Time Log Batch");
|
||||
$.each(selected, function(i, d) {
|
||||
var detail = frappe.model.get_new_doc("Time Log Batch Detail", tlb,
|
||||
"time_logs");
|
||||
|
||||
$.extend(detail, {
|
||||
"time_log": d.name,
|
||||
"activity_type": d.activity_type,
|
||||
"billing_amount": d.billing_amount,
|
||||
"hours": d.hours
|
||||
});
|
||||
})
|
||||
frappe.set_route("Form", "Time Log Batch", tlb.name);
|
||||
})
|
||||
|
||||
}, "icon-file-alt");
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
Group of Time Logs that can be batched for billing.
|
@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, unittest
|
||||
from erpnext.projects.doctype.time_log.test_time_log import make_time_log_test_record
|
||||
|
||||
class TimeLogBatchTest(unittest.TestCase):
|
||||
def test_time_log_status(self):
|
||||
time_log = make_time_log_test_record(simulate=True)
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
|
||||
|
||||
tlb = create_time_log_batch(time_log.name)
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Batched for Billing")
|
||||
tlb.cancel()
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
|
||||
|
||||
def create_time_log_batch(time_log):
|
||||
tlb = frappe.get_doc({
|
||||
"doctype": "Time Log Batch",
|
||||
"time_logs": [
|
||||
{
|
||||
"doctype": "Time Log Batch Detail",
|
||||
"parentfield": "time_logs",
|
||||
"parenttype": "Time Log Batch",
|
||||
"time_log": time_log
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
tlb.insert()
|
||||
tlb.submit()
|
||||
return tlb
|
||||
|
||||
test_ignore = ["Sales Invoice"]
|
@ -1,67 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// 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", "billing_amount", "billing_amount");
|
||||
cur_frm.add_fetch("time_log", "hours", "hours");
|
||||
|
||||
cur_frm.set_query("time_log", "time_logs", function(doc) {
|
||||
return {
|
||||
query: "erpnext.projects.utils.get_time_log_list",
|
||||
filters: {
|
||||
"billable": 1,
|
||||
"status": "Submitted"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(cur_frm.cscript, {
|
||||
refresh: function(doc) {
|
||||
cur_frm.set_intro({
|
||||
"Draft": __("Select Time Logs and Submit to create a new Sales Invoice."),
|
||||
"Submitted": __("Click on 'Make Sales Invoice' button to create a new Sales Invoice."),
|
||||
"Billed": __("This Time Log Batch has been billed."),
|
||||
"Cancelled": __("This Time Log Batch has been cancelled.")
|
||||
}[doc.status]);
|
||||
|
||||
if(doc.status=="Submitted") {
|
||||
cur_frm.add_custom_button(__("Make Sales Invoice"), function() { cur_frm.cscript.make_invoice() },
|
||||
"icon-file-alt");
|
||||
}
|
||||
},
|
||||
make_invoice: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.projects.doctype.time_log_batch.time_log_batch.make_sales_invoice",
|
||||
frm: cur_frm
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Time Log Batch Detail", "time_log", function(frm) {
|
||||
calculate_time_and_amount(frm);
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Time Log Batch Detail", "time_logs_remove", function(frm) {
|
||||
calculate_time_and_amount(frm);
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Time Log Batch", "onload", function(frm) {
|
||||
if (frm.doc.__islocal && frm.doc.time_logs) {
|
||||
calculate_time_and_amount(frm);
|
||||
}
|
||||
});
|
||||
|
||||
var calculate_time_and_amount = function(frm) {
|
||||
var tl = frm.doc.time_logs || [];
|
||||
total_hr = 0;
|
||||
total_amt = 0;
|
||||
for(var i=0; i<tl.length; i++) {
|
||||
if (tl[i].time_log) {
|
||||
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);
|
||||
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-02-28 17:57:33",
|
||||
"custom": 0,
|
||||
"description": "Batch Time Logs for Billing.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "TLB-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nBilled\nCancelled",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "Will be updated after Sales Invoice is Submitted.",
|
||||
"fieldname": "sales_invoice",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sales Invoice",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Sales Invoice",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "time_logs",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Time Logs",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Time Log Batch Detail",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"description": "updated via Time Logs",
|
||||
"fieldname": "total_hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Total Hours",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"description": "updated via Time Logs",
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Total Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Time Log Batch",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-time",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:59.535984",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Time Log Batch",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Projects User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
class TimeLogBatch(Document):
|
||||
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.total_hours = 0.0
|
||||
self.total_billing_amount = 0.0
|
||||
for d in self.get("time_logs"):
|
||||
tl = frappe.get_doc("Time Log", d.time_log)
|
||||
self.update_time_log_values(d, tl)
|
||||
self.validate_time_log_is_submitted(tl)
|
||||
self.total_hours += flt(tl.hours)
|
||||
self.total_billing_amount += flt(tl.billing_amount)
|
||||
|
||||
def update_time_log_values(self, d, tl):
|
||||
d.update({
|
||||
"hours": tl.hours,
|
||||
"activity_type": tl.activity_type,
|
||||
"billing_amount": tl.billing_amount,
|
||||
"note": tl.note
|
||||
})
|
||||
|
||||
def validate_time_log_is_submitted(self, tl):
|
||||
if tl.status == "Batched for Billing":
|
||||
frappe.throw(_("Time Log {0} already billed").format(tl.name))
|
||||
elif tl.docstatus != 1:
|
||||
frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name))
|
||||
|
||||
def set_status(self):
|
||||
self.status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
}[str(self.docstatus or 0)]
|
||||
|
||||
if self.sales_invoice:
|
||||
self.status = "Billed"
|
||||
|
||||
def on_submit(self):
|
||||
self.update_status(self.name)
|
||||
|
||||
def before_cancel(self):
|
||||
self.update_status(None)
|
||||
|
||||
def before_update_after_submit(self):
|
||||
self.update_status(self.name)
|
||||
|
||||
def update_status(self, time_log_batch):
|
||||
self.set_status()
|
||||
for d in self.get("time_logs"):
|
||||
tl = frappe.get_doc("Time Log", d.time_log)
|
||||
tl.time_log_batch = time_log_batch
|
||||
tl.sales_invoice = self.sales_invoice
|
||||
tl.flags.ignore_validate_update_after_submit = True
|
||||
tl.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target=None):
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.stock_uom = "Hour"
|
||||
target_doc.description = "via Time Logs"
|
||||
target_doc.qty = 1
|
||||
|
||||
target = frappe.new_doc("Sales Invoice")
|
||||
target.append("items", get_mapped_doc("Time Log Batch", source_name, {
|
||||
"Time Log Batch": {
|
||||
"doctype": "Sales Invoice Item",
|
||||
"field_map": {
|
||||
"total_billing_amount": "rate",
|
||||
"name": "time_log_batch"
|
||||
},
|
||||
"postprocess": update_item
|
||||
}
|
||||
}))
|
||||
|
||||
return target
|
@ -1,8 +0,0 @@
|
||||
frappe.listview_settings['Time Log Batch'] = {
|
||||
add_fields: ["status", "total_hours"],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status== "Billed") {
|
||||
return [__("Billed"), "green", "status,=," + "Billed"]
|
||||
}
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
Time Log detail for parent Time Log Batch.
|
@ -1,142 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2013-03-05 09:11:06",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "time_log",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Time Log",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Time Log",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_width": "200px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "200px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Hours",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "activity_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Activity Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-11-16 06:29:59.590738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Time Log Batch Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TimeLogBatchDetail(Document):
|
||||
pass
|
118
erpnext/projects/doctype/time_sheet/test_time_sheet.py
Normal file
118
erpnext/projects/doctype/time_sheet/test_time_sheet.py
Normal file
@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import datetime
|
||||
from frappe.utils import now_datetime, nowdate
|
||||
from erpnext.projects.doctype.time_sheet.time_sheet import OverlapError
|
||||
from erpnext.projects.doctype.time_sheet.time_sheet import make_salary_slip, make_sales_invoice
|
||||
|
||||
class TestTimeSheet(unittest.TestCase):
|
||||
def test_time_sheet_billing_amount(self):
|
||||
salary_structure = make_salary_structure("_T-Employee-0001")
|
||||
time_sheet = make_time_sheet("_T-Employee-0001", True)
|
||||
|
||||
self.assertEquals(time_sheet.total_hours, 2)
|
||||
self.assertEquals(time_sheet.timesheets[0].billing_rate, 50)
|
||||
self.assertEquals(time_sheet.timesheets[0].billing_amount, 100)
|
||||
|
||||
def test_salary_slip_from_timesheet(self):
|
||||
salary_structure = make_salary_structure("_T-Employee-0001")
|
||||
time_sheet = make_time_sheet("_T-Employee-0001", simulate = True)
|
||||
salary_slip = make_salary_slip(time_sheet.name)
|
||||
salary_slip.submit()
|
||||
|
||||
self.assertEquals(salary_slip.total_working_hours, 2)
|
||||
self.assertEquals(salary_slip.hour_rate, 50)
|
||||
self.assertEquals(salary_slip.net_pay, 150)
|
||||
self.assertEquals(salary_slip.timesheets[0].time_sheet, time_sheet.name)
|
||||
self.assertEquals(salary_slip.timesheets[0].working_hours, 2)
|
||||
|
||||
time_sheet = frappe.get_doc('Time Sheet', time_sheet.name)
|
||||
self.assertEquals(time_sheet.status, 'Payslip')
|
||||
salary_slip.cancel()
|
||||
|
||||
time_sheet = frappe.get_doc('Time Sheet', time_sheet.name)
|
||||
self.assertEquals(time_sheet.status, 'Submitted')
|
||||
|
||||
def test_sales_invoice_from_timesheet(self):
|
||||
time_sheet = make_time_sheet("_T-Employee-0001", simulate = True, billable = 1)
|
||||
sales_invoice = make_sales_invoice(time_sheet.name)
|
||||
sales_invoice.customer = "_Test Customer"
|
||||
sales_invoice.due_date = nowdate()
|
||||
|
||||
item = sales_invoice.append('items', {})
|
||||
item.item_code = '_Test Item'
|
||||
item.qty = 2
|
||||
item.rate = 100
|
||||
|
||||
sales_invoice.submit()
|
||||
|
||||
time_sheet = frappe.get_doc('Time Sheet', time_sheet.name)
|
||||
self.assertEquals(sales_invoice.total_billing_amount, 100)
|
||||
self.assertEquals(time_sheet.status, 'Billed')
|
||||
|
||||
def make_salary_structure(employee):
|
||||
name = frappe.db.get_value('Salary Structure', {'employee': employee, 'salary_slip_based_on_timesheet': 1}, 'name')
|
||||
if name:
|
||||
frappe.delete_doc('Salary Structure', name)
|
||||
|
||||
salary_structure = frappe.new_doc("Salary Structure")
|
||||
salary_structure.salary_slip_based_on_timesheet = 1
|
||||
salary_structure.employee = employee
|
||||
salary_structure.from_date = nowdate()
|
||||
salary_structure.earning_type = "Basic"
|
||||
salary_structure.hour_rate = 50.0
|
||||
salary_structure.company= "_Test Company"
|
||||
|
||||
salary_structure.set('earnings', [])
|
||||
salary_structure.set('deductions', [])
|
||||
|
||||
es = salary_structure.append('earnings', {
|
||||
"e_type": "_Test Allowance",
|
||||
"modified_value": 100
|
||||
})
|
||||
|
||||
ds = salary_structure.append('deductions', {
|
||||
"d_type": "_Test Professional Tax",
|
||||
"d_modified_amt": 50
|
||||
})
|
||||
|
||||
salary_structure.save(ignore_permissions=True)
|
||||
|
||||
return salary_structure
|
||||
|
||||
def make_time_sheet(employee, simulate=False, billable = 0):
|
||||
update_activity_type("_Test Activity Type")
|
||||
time_sheet = frappe.new_doc("Time Sheet")
|
||||
time_sheet.employee = employee
|
||||
time_sheet_detail = time_sheet.append('timesheets', {})
|
||||
time_sheet_detail.billable = billable
|
||||
time_sheet_detail.activity_type = "_Test Activity Type"
|
||||
time_sheet_detail.from_time = now_datetime()
|
||||
time_sheet_detail.hours = 2
|
||||
time_sheet_detail.to_time = time_sheet_detail.from_time + datetime.timedelta(hours= time_sheet_detail.hours)
|
||||
|
||||
for data in time_sheet.get('timesheets'):
|
||||
if simulate:
|
||||
while True:
|
||||
try:
|
||||
time_sheet.save()
|
||||
break
|
||||
except OverlapError:
|
||||
data.from_time = data.from_time + datetime.timedelta(minutes=10)
|
||||
data.to_time = data.from_time + datetime.timedelta(hours= data.hours)
|
||||
else:
|
||||
time_sheet.save()
|
||||
|
||||
time_sheet.submit()
|
||||
|
||||
return time_sheet
|
||||
|
||||
def update_activity_type(activity_type):
|
||||
activity_type = frappe.get_doc('Activity Type',activity_type)
|
||||
activity_type.billing_rate = 50.0
|
||||
activity_type.save(ignore_permissions=True)
|
156
erpnext/projects/doctype/time_sheet/time_sheet.js
Normal file
156
erpnext/projects/doctype/time_sheet/time_sheet.js
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.add_fetch("activity_type", "billing_rate", "billing_rate");
|
||||
|
||||
frappe.ui.form.on("Time Sheet", {
|
||||
setup: function(frm) {
|
||||
frm.get_field('timesheets').grid.editable_fields = [
|
||||
{fieldname: 'activity_type', columns: 2},
|
||||
{fieldname: 'from_time', columns: 2},
|
||||
{fieldname: 'hours', columns: 2},
|
||||
{fieldname: 'to_time', columns: 2},
|
||||
];
|
||||
|
||||
frm.fields_dict.employee.get_query = function() {
|
||||
return {
|
||||
query:"erpnext.projects.doctype.time_sheet.time_sheet.get_employee_list"
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict['timesheets'].grid.get_field('task').get_query = function(frm, cdt, cdn) {
|
||||
child = locals[cdt][cdn];
|
||||
return{
|
||||
filters: {
|
||||
'project': child.project
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm){
|
||||
if (frm.doc.__islocal && frm.doc.timesheets) {
|
||||
frm.set_value("employee", "")
|
||||
calculate_time_and_amount(frm);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus==1) {
|
||||
if(!frm.doc.sales_invoice && frm.doc.total_billing_amount > 0
|
||||
&& frm.doc.employee){
|
||||
frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice") },
|
||||
"icon-file-alt");
|
||||
}
|
||||
|
||||
if(!frm.doc.salary_slip && frm.doc.employee){
|
||||
frm.add_custom_button(__("Make Salary Slip"), function() { frm.trigger("make_salary_slip") },
|
||||
"icon-file-alt");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
make_invoice: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.projects.doctype.time_sheet.time_sheet.make_sales_invoice",
|
||||
frm: frm
|
||||
});
|
||||
},
|
||||
|
||||
make_salary_slip: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.projects.doctype.time_sheet.time_sheet.make_salary_slip",
|
||||
frm: frm
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Time Sheet Detail", {
|
||||
timesheets_remove: function(frm) {
|
||||
calculate_time_and_amount(frm);
|
||||
},
|
||||
|
||||
from_time: function(frm, cdt, cdn) {
|
||||
calculate_end_time(frm, cdt, cdn)
|
||||
},
|
||||
|
||||
to_time: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
|
||||
if(frm._setting_hours) return;
|
||||
|
||||
if(flt(child.hours) == 0.0){
|
||||
frappe.model.set_value(cdt, cdn, "hours", moment(child.to_time).diff(moment(child.from_time),
|
||||
"seconds") / 3600);
|
||||
}else{
|
||||
var d = moment(child.to_time);
|
||||
d.add(child.hours, "hours");
|
||||
frappe.model.set_value(cdt, cdn, "from_time", d.format(moment.defaultDatetimeFormat));
|
||||
}
|
||||
},
|
||||
|
||||
hours: function(frm, cdt, cdn) {
|
||||
calculate_end_time(frm, cdt, cdn)
|
||||
},
|
||||
|
||||
billing_rate: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
if(child.hours && child.billing_rate){
|
||||
frappe.mode.set_value(cdt, cdn, 'total_billing_amount', flt(child.billing_rate * child.hours))
|
||||
}
|
||||
calculate_billing_amount(frm, cdt, cdn)
|
||||
},
|
||||
|
||||
costing_rate: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
frappe.mode.set_value(cdt, cdn, 'total_costing_amount', flt(child.costing_rate * child.hours))
|
||||
calculate_billing_amount(frm, cdt, cdn)
|
||||
},
|
||||
|
||||
billable: function(frm, cdt, cdn) {
|
||||
calculate_billing_amount(frm, cdt, cdn)
|
||||
}
|
||||
});
|
||||
|
||||
calculate_end_time = function(frm, cdt, cdn){
|
||||
var child = locals[cdt][cdn];
|
||||
|
||||
if(!child.from_time) {
|
||||
frappe.model.set_value(cdt, cdn, "from_time", frappe.datetime.now_datetime());
|
||||
}
|
||||
|
||||
var d = moment(child.from_time);
|
||||
d.add(child.hours, "hours");
|
||||
frm._setting_hours = true;
|
||||
frappe.model.set_value(cdt, cdn, "to_time", d.format(moment.defaultDatetimeFormat));
|
||||
frm._setting_hours = false;
|
||||
|
||||
calculate_billing_amount(frm, cdt, cdn)
|
||||
}
|
||||
|
||||
var calculate_billing_amount = function(frm, cdt, cdn){
|
||||
child = locals[cdt][cdn]
|
||||
billing_amount = 0.0
|
||||
|
||||
if(child.hours && child.billable){
|
||||
billing_amount = (child.hours * child.billing_rate)
|
||||
}
|
||||
|
||||
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount)
|
||||
calculate_time_and_amount(frm)
|
||||
}
|
||||
|
||||
var calculate_time_and_amount = function(frm) {
|
||||
var tl = frm.doc.timesheets || [];
|
||||
total_hr = 0;
|
||||
total_billing_amount = 0;
|
||||
for(var i=0; i<tl.length; i++) {
|
||||
if (tl[i].hours) {
|
||||
total_hr += tl[i].hours;
|
||||
total_billing_amount += tl[i].billing_amount;
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.set_value("total_hours", total_hr);
|
||||
cur_frm.set_value("total_billing_amount", total_billing_amount);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
272
erpnext/projects/doctype/time_sheet/time_sheet.py
Normal file
272
erpnext/projects/doctype/time_sheet/time_sheet.py
Normal file
@ -0,0 +1,272 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint, get_datetime_str
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class OverProductionLoggedError(frappe.ValidationError): pass
|
||||
|
||||
class TimeSheet(Document):
|
||||
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.total_hours = 0.0
|
||||
self.total_billing_amount = 0.0
|
||||
self.validate_dates()
|
||||
self.validate_timesheets()
|
||||
self.update_cost()
|
||||
for d in self.get("timesheets"):
|
||||
self.total_hours += flt(d.hours)
|
||||
if d.billable: self.total_billing_amount += flt(d.billing_amount)
|
||||
|
||||
def set_status(self):
|
||||
self.status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
}[str(self.docstatus or 0)]
|
||||
|
||||
if self.sales_invoice:
|
||||
self.status = "Billed"
|
||||
|
||||
if self.salary_slip:
|
||||
self.status = "Payslip"
|
||||
|
||||
if self.sales_invoice and self.salary_slip:
|
||||
self.status = "Completed"
|
||||
|
||||
def set_dates(self):
|
||||
if self.docstatus < 2:
|
||||
start_date = min([d.from_time for d in self.timesheets])
|
||||
end_date = max([d.to_time for d in self.timesheets])
|
||||
|
||||
if start_date and end_date:
|
||||
self.start_date = getdate(start_date)
|
||||
self.end_date = getdate(end_date)
|
||||
|
||||
def before_submit(self):
|
||||
self.set_dates()
|
||||
|
||||
def before_cancel(self):
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_production_order(None)
|
||||
self.update_task_and_project()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_mandatory_fields()
|
||||
self.update_production_order(self.name)
|
||||
self.update_task_and_project()
|
||||
|
||||
def validate_mandatory_fields(self):
|
||||
if self.production_order:
|
||||
production_order = frappe.get_doc("Production Order", self.production_order)
|
||||
pending_qty = flt(production_order.qty) - flt(production_order.produced_qty)
|
||||
|
||||
for data in self.timesheets:
|
||||
if not data.from_time and not data.to_time:
|
||||
frappe.throw(_("Row {0}: From Time and To Time is mandatory.").format(data.idx))
|
||||
|
||||
if not data.activity_type and self.employee:
|
||||
frappe.throw(_("Row {0}: Activity Type is mandatory.").format(data.idx))
|
||||
|
||||
if flt(data.hours) == 0.0:
|
||||
frappe.throw(_("Row {0}: Hours value must be greater than zero.").format(data.idx))
|
||||
|
||||
if self.production_order and flt(data.completed_qty) == 0:
|
||||
frappe.throw(_("Row {0}: Completed Qty must be greater than zero.").format(data.idx))
|
||||
|
||||
if self.production_order and flt(pending_qty) < flt(data.completed_qty):
|
||||
frappe.throw(_("Row {0}: Completed Qty cannot be more than {0} for operation {1}").format(data.idx, pending_qty, self.operation),
|
||||
OverProductionLoggedError)
|
||||
|
||||
def update_production_order(self, time_sheet):
|
||||
if self.production_order:
|
||||
pro = frappe.get_doc('Production Order', self.production_order)
|
||||
|
||||
for timesheet in self.timesheets:
|
||||
for data in pro.operations:
|
||||
if data.name == timesheet.operation_id:
|
||||
summary = self.get_actual_timesheet_summary(timesheet.operation_id)
|
||||
data.time_sheet = time_sheet
|
||||
data.completed_qty = summary.completed_qty
|
||||
data.actual_operation_time = summary.mins
|
||||
data.actual_start_time = summary.from_time
|
||||
data.actual_end_time = summary.to_time
|
||||
|
||||
pro.flags.ignore_validate_update_after_submit = True
|
||||
pro.update_operation_status()
|
||||
pro.calculate_operating_cost()
|
||||
pro.set_actual_dates()
|
||||
pro.save()
|
||||
|
||||
def get_actual_timesheet_summary(self, operation_id):
|
||||
"""Returns 'Actual Operating Time'. """
|
||||
return frappe.db.sql("""select
|
||||
sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time,
|
||||
max(tsd.to_time) as to_time from `tabTime Sheet Detail` as tsd, `tabTime Sheet` as ts where
|
||||
ts.production_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""",
|
||||
(self.production_order, operation_id), as_dict=1)[0]
|
||||
|
||||
def update_task_and_project(self):
|
||||
for data in self.timesheets:
|
||||
if data.task:
|
||||
task = frappe.get_doc("Task", data.task)
|
||||
task.update_time_and_costing()
|
||||
task.save()
|
||||
|
||||
elif data.project:
|
||||
frappe.get_doc("Project", data.project).update_project()
|
||||
|
||||
def validate_dates(self):
|
||||
for data in self.timesheets:
|
||||
if time_diff_in_hours(data.to_time, data.from_time) < 0:
|
||||
frappe.throw(_("To date cannot be before from date"))
|
||||
|
||||
def validate_timesheets(self):
|
||||
for data in self.get('timesheets'):
|
||||
self.validate_overlap(data)
|
||||
self.check_workstation_timings(data)
|
||||
|
||||
def validate_overlap(self, data):
|
||||
if self.production_order:
|
||||
self.validate_overlap_for("workstation", data)
|
||||
else:
|
||||
self.validate_overlap_for("user", data)
|
||||
self.validate_overlap_for("employee", data)
|
||||
|
||||
def validate_overlap_for(self, fieldname, args):
|
||||
existing = self.get_overlap_for(fieldname, args)
|
||||
if existing:
|
||||
frappe.throw(_("Row {0}: From Time and To Time overlap with existing from and to time").format(args.idx),
|
||||
OverlapError)
|
||||
|
||||
def get_overlap_for(self, fieldname, args):
|
||||
if not args.get(fieldname):
|
||||
return
|
||||
|
||||
existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from
|
||||
`tabTime Sheet Detail` tsd, `tabTime Sheet` ts where tsd.`{0}`=%(val)s and tsd.parent = ts.name and
|
||||
(
|
||||
(%(from_time)s > tsd.from_time and %(from_time)s < tsd.to_time) or
|
||||
(%(to_time)s > tsd.from_time and %(to_time)s < tsd.to_time) or
|
||||
(%(from_time)s <= tsd.from_time and %(to_time)s >= tsd.to_time))
|
||||
and tsd.name!=%(name)s
|
||||
and ts.docstatus < 2""".format(fieldname),
|
||||
{
|
||||
"val": args.get(fieldname),
|
||||
"from_time": args.from_time,
|
||||
"to_time": args.to_time,
|
||||
"name": args.name or "No Name"
|
||||
}, as_dict=True)
|
||||
|
||||
return existing[0] if existing else None
|
||||
|
||||
def check_workstation_timings(self, args):
|
||||
"""Checks if **Time Log** is between operating hours of the **Workstation**."""
|
||||
if args.workstation and args.from_time and args.to_time:
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
|
||||
check_if_within_operating_hours(args.workstation, args.operation, args.from_time, args.to_time)
|
||||
|
||||
def move_to_next_non_overlapping_slot(self, index):
|
||||
from datetime import timedelta
|
||||
if self.timesheets:
|
||||
for data in self.timesheets:
|
||||
if data.idx == index:
|
||||
overlapping = self.get_overlap_for("workstation", data)
|
||||
if not overlapping:
|
||||
frappe.throw(_("Logical error: Must find overlapping"))
|
||||
|
||||
if overlapping:
|
||||
time_sheet = self.get_last_working_slot(overlapping.name, data.workstation)
|
||||
data.from_time = get_datetime(time_sheet.to_time) + get_mins_between_operations()
|
||||
data.to_time = get_datetime(data.from_time) + timedelta(hours=data.hours)
|
||||
break
|
||||
|
||||
def get_last_working_slot(self, time_sheet, workstation):
|
||||
return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time
|
||||
from `tabTime Sheet Detail` where workstation = %(workstation)s""",
|
||||
{'workstation': workstation}, as_dict=True)[0]
|
||||
|
||||
def update_cost(self):
|
||||
for data in self.timesheets:
|
||||
if data.activity_type and not data.billing_amount:
|
||||
rate = get_activity_cost(self.employee, data.activity_type)
|
||||
hours = data.hours or 0
|
||||
if rate:
|
||||
data.billing_rate = flt(rate.get('billing_rate'))
|
||||
data.costing_rate = flt(rate.get('costing_rate'))
|
||||
data.billing_amount = data.billing_rate * hours
|
||||
data.costing_amount = data.costing_rate * hours
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target=None):
|
||||
target = frappe.new_doc("Sales Invoice")
|
||||
|
||||
target.append("timesheets", get_mapped_doc("Time Sheet", source_name, {
|
||||
"Time Sheet": {
|
||||
"doctype": "Sales Invoice Timesheet",
|
||||
"field_map": {
|
||||
"total_billing_amount": "billing_amount",
|
||||
"name": "time_sheet"
|
||||
},
|
||||
}
|
||||
}))
|
||||
|
||||
target.run_method("calculate_billing_amount_from_timesheet")
|
||||
|
||||
return target
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_salary_slip(source_name, target_doc=None):
|
||||
target = frappe.new_doc("Salary Slip")
|
||||
set_missing_values(source_name, target)
|
||||
|
||||
target.append("timesheets", get_mapped_doc("Time Sheet", source_name, {
|
||||
"Time Sheet": {
|
||||
"doctype": "Salary Slip Timesheet",
|
||||
"field_map": {
|
||||
"total_hours": "working_hours",
|
||||
"name": "time_sheet"
|
||||
},
|
||||
}
|
||||
}))
|
||||
|
||||
target.run_method("get_emp_and_leave_details")
|
||||
|
||||
return target
|
||||
|
||||
def set_missing_values(time_sheet, target):
|
||||
doc = frappe.get_doc('Time Sheet', time_sheet)
|
||||
target.employee = doc.employee
|
||||
target.employee_name = doc.employee_name
|
||||
target.salary_slip_based_on_timesheet = 1
|
||||
target.start_date = doc.start_date
|
||||
target.end_date = doc.end_date
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_activity_cost(employee=None, activity_type=None):
|
||||
rate = frappe.db.get_values("Activity Cost", {"employee": employee,
|
||||
"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True)
|
||||
if not rate:
|
||||
rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type},
|
||||
["costing_rate", "billing_rate"], as_dict=True)
|
||||
|
||||
return rate[0] if rate else {}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_employee_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select distinct(employee) as employee
|
||||
from `tabSalary Structure` where salary_slip_based_on_timesheet=1
|
||||
and employee like %(txt)s limit %(start)s, %(page_len)s""",
|
||||
{'txt': "%%%s%%"%(txt), 'start': start, 'page_len': page_len})
|
16
erpnext/projects/doctype/time_sheet/time_sheet_list.js
Normal file
16
erpnext/projects/doctype/time_sheet/time_sheet_list.js
Normal file
@ -0,0 +1,16 @@
|
||||
frappe.listview_settings['Time Sheet'] = {
|
||||
add_fields: ["status", "total_hours"],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status== "Billed") {
|
||||
return [__("Billed"), "green", "status,=," + "Billed"]
|
||||
}
|
||||
|
||||
if (doc.status== "Payslip") {
|
||||
return [__("Payslip"), "green", "status,=," + "Payslip"]
|
||||
}
|
||||
|
||||
if (doc.status== "Completed") {
|
||||
return [__("Completed"), "green", "status,=," + "Completed"]
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,493 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-03-05 09:11:06",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "billable",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Billable",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "activity_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Activity Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Activity Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "From Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Hours",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "To Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Project",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Project",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "project",
|
||||
"fieldname": "task",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Task",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Task",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:parent.production_order",
|
||||
"fieldname": "workstation",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Workstation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Workstation",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:parent.production_order",
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Operation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Operation",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:parent.production_order",
|
||||
"fieldname": "operation_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Operation Id",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "completed_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Completed Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "billing_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Billing Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "costing_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Costing Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"depends_on": "",
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"fieldname": "costing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Costing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-06-24 15:55:30.746870",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Time Sheet Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TimeSheetDetail(Document):
|
||||
pass
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-04-03 11:27:52",
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2014-06-03 07:18:17.019543",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Daily Time Log Summary",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Time Log",
|
||||
"report_name": "Daily Time Log Summary",
|
||||
"report_type": "Script Report"
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
elif filters.get("from_date") or filters.get("to_date"):
|
||||
filters["from_time"] = "00:00:00"
|
||||
filters["to_time"] = "24:00:00"
|
||||
|
||||
columns = [_("Time Log") + ":Link/Time Log:120", _("Employee") + "::150", _("From Datetime") + "::140",
|
||||
_("To Datetime") + "::140", _("Hours") + "::70", _("Activity Type") + "::120", _("Task") + ":Link/Task:150",
|
||||
_("Task Subject") + "::180", _("Project") + ":Link/Project:120", _("Status") + "::70"]
|
||||
|
||||
user_map = get_user_map()
|
||||
employee_map = get_employee_map()
|
||||
task_map = get_task_map()
|
||||
|
||||
conditions = build_conditions(filters)
|
||||
time_logs = frappe.db.sql("""select * from `tabTime Log`
|
||||
where docstatus < 2 %s order by owner asc""" % (conditions, ), filters, as_dict=1)
|
||||
|
||||
if time_logs:
|
||||
users = [time_logs[0].owner]
|
||||
|
||||
data = []
|
||||
total_hours = total_employee_hours = count = 0
|
||||
for tl in time_logs:
|
||||
if tl.employee:
|
||||
employee=employee_map[tl.employee]
|
||||
else:
|
||||
employee=user_map[tl.owner]
|
||||
if tl.owner not in users:
|
||||
users.append(tl.owner)
|
||||
data.append(["", "", "", "Total", total_employee_hours, "", "", "", "", ""])
|
||||
total_employee_hours = 0
|
||||
|
||||
data.append([tl.name, employee, tl.from_time, tl.to_time, tl.hours,
|
||||
tl.activity_type, tl.task, task_map.get(tl.task), tl.project, tl.status])
|
||||
|
||||
count += 1
|
||||
total_hours += flt(tl.hours)
|
||||
total_employee_hours += flt(tl.hours)
|
||||
|
||||
if count == len(time_logs):
|
||||
data.append(["", "", "", "Total Hours", total_employee_hours, "", "", "", "", ""])
|
||||
|
||||
if total_hours:
|
||||
data.append(["", "", "", "Grand Total", total_hours, "", "", "", "", ""])
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_user_map():
|
||||
users = frappe.db.sql("""select name,
|
||||
concat(first_name, if(last_name, (' ' + last_name), '')) as fullname
|
||||
from tabUser""", as_dict=1)
|
||||
user_map = {}
|
||||
for p in users:
|
||||
user_map.setdefault(p.name, []).append(p.fullname)
|
||||
|
||||
return user_map
|
||||
|
||||
def get_employee_map():
|
||||
employees = frappe.db.sql("""select name,
|
||||
employee_name as fullname
|
||||
from tabEmployee""", as_dict=1)
|
||||
employee_map = {}
|
||||
for p in employees:
|
||||
employee_map.setdefault(p.name, []).append(p.fullname)
|
||||
|
||||
return employee_map
|
||||
|
||||
def get_task_map():
|
||||
tasks = frappe.db.sql("""select name, subject from tabTask""", as_dict=1)
|
||||
task_map = {}
|
||||
for t in tasks:
|
||||
task_map.setdefault(t.name, []).append(t.subject)
|
||||
|
||||
return task_map
|
||||
|
||||
def build_conditions(filters):
|
||||
conditions = ""
|
||||
if filters.get("from_date"):
|
||||
conditions += " and from_time >= timestamp(%(from_date)s, %(from_time)s)"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and to_time <= timestamp(%(to_date)s, %(to_time)s)"
|
||||
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
match_conditions = build_match_conditions("Time Log")
|
||||
if match_conditions:
|
||||
conditions += " and %s" % match_conditions
|
||||
|
||||
return conditions
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Daily Time Log Summary"] = {
|
||||
frappe.query_reports["Daily Time Sheet Summary"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"from_date",
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2016-06-25 01:52:25.911238",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2016-06-25 01:52:31.214122",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Daily Time Sheet Summary",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Time Sheet",
|
||||
"report_name": "Daily Time Sheet Summary",
|
||||
"report_type": "Script Report"
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
elif filters.get("from_date") or filters.get("to_date"):
|
||||
filters["from_time"] = "00:00:00"
|
||||
filters["to_time"] = "24:00:00"
|
||||
|
||||
columns = [_("Time Sheet") + ":Link/Time Sheet:120", _("Employee") + "::150", _("From Datetime") + "::140",
|
||||
_("To Datetime") + "::140", _("Hours") + "::70", _("Activity Type") + "::120", _("Task") + ":Link/Task:150",
|
||||
_("Project") + ":Link/Project:120", _("Status") + "::70"]
|
||||
|
||||
conditions = "ts.docstatus = 1"
|
||||
if filters.get("from_date"):
|
||||
conditions += " and tsd.from_time >= timestamp(%(from_date)s, %(from_time)s)"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and tsd.to_time <= timestamp(%(to_date)s, %(to_time)s)"
|
||||
|
||||
data = get_data(conditions, filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(conditions, filters):
|
||||
time_sheet = frappe.db.sql(""" select ts.name, ts.employee, tsd.from_time, tsd.to_time, tsd.hours,
|
||||
tsd.activity_type, tsd.task, tsd.project, ts.status from `tabTime Sheet Detail` tsd,
|
||||
`tabTime Sheet` ts where ts.name = tsd.parent and %s order by ts.name"""%(conditions), filters, as_list=1)
|
||||
|
||||
return time_sheet
|
@ -6,10 +6,6 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_time_log_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.get_values("Time Log", filters, ["name", "activity_type", "owner"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def query_task(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
|
@ -18,7 +18,7 @@ def delete_company_transactions(company_name):
|
||||
frappe.PermissionError)
|
||||
|
||||
delete_bins(company_name)
|
||||
delete_time_logs(company_name)
|
||||
delete_time_sheets(company_name)
|
||||
delete_lead_addresses(company_name)
|
||||
|
||||
for doctype in frappe.db.sql_list("""select parent from
|
||||
@ -70,21 +70,12 @@ def delete_bins(company_name):
|
||||
frappe.db.sql("""delete from tabBin where warehouse in
|
||||
(select name from tabWarehouse where company=%s)""", company_name)
|
||||
|
||||
def delete_time_logs(company_name):
|
||||
def delete_time_sheets(company_name):
|
||||
# Delete Time Logs as it is linked to Production Order / Project / Task, which are linked to company
|
||||
frappe.db.sql("""
|
||||
delete from `tabTime Log`
|
||||
delete from `tabTime Sheet`
|
||||
where
|
||||
(ifnull(project, '') != ''
|
||||
and exists(select name from `tabProject` where name=`tabTime Log`.project and company=%(company)s))
|
||||
or (ifnull(task, '') != ''
|
||||
and exists(select name from `tabTask` where name=`tabTime Log`.task and company=%(company)s))
|
||||
or (ifnull(production_order, '') != ''
|
||||
and exists(select name from `tabProduction Order`
|
||||
where name=`tabTime Log`.production_order and company=%(company)s))
|
||||
or (ifnull(sales_invoice, '') != ''
|
||||
and exists(select name from `tabSales Invoice`
|
||||
where name=`tabTime Log`.sales_invoice and company=%(company)s))
|
||||
company=%(company)s
|
||||
""", {"company": company_name})
|
||||
|
||||
def delete_lead_addresses(company_name):
|
||||
|
@ -43,7 +43,7 @@ domains = {
|
||||
},
|
||||
|
||||
'Services': {
|
||||
'desktop_icons': ['Project', 'Time Log', 'Customer', 'Sales Order', 'Sales Invoice', 'Lead', 'Opportunity',
|
||||
'desktop_icons': ['Project', 'Time Sheet', 'Customer', 'Sales Order', 'Sales Invoice', 'Lead', 'Opportunity',
|
||||
'Expense Claim', 'Employee', 'HR', 'ToDo'],
|
||||
'remove_roles': ['Manufacturing User', 'Manufacturing Manager'],
|
||||
'properties': [
|
||||
|
@ -43,8 +43,6 @@ def get_notification_config():
|
||||
"Purchase Receipt": {"docstatus": 0},
|
||||
"Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
|
||||
"BOM": {"docstatus": 0},
|
||||
"Timesheet": {"docstatus": 0},
|
||||
"Time Log": {"status": "Draft"},
|
||||
"Time Log Batch": {"status": "Draft"}
|
||||
"Time Sheet": {"status": "Draft"}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ class StockEntry(StockController):
|
||||
self.production_order = None
|
||||
|
||||
def check_if_operations_completed(self):
|
||||
"""Check if Time Logs are completed against before manufacturing to capture operating costs."""
|
||||
"""Check if Time Sheets are completed against before manufacturing to capture operating costs."""
|
||||
prod_order = frappe.get_doc("Production Order", self.production_order)
|
||||
|
||||
for d in prod_order.get("operations"):
|
||||
|
@ -20,8 +20,9 @@ def get_context(context):
|
||||
project.tasks = get_tasks(project.name, start=0, item_status='open',
|
||||
search=frappe.form_dict.get("search"))
|
||||
|
||||
project.timelogs = get_timelogs(project.name, start=0,
|
||||
search=frappe.form_dict.get("search"))
|
||||
project.timelogs = []
|
||||
# project.timelogs = get_timelogs(project.name, start=0,
|
||||
# search=frappe.form_dict.get("search"))
|
||||
|
||||
|
||||
context.doc = project
|
||||
|
Loading…
x
Reference in New Issue
Block a user