mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-14 02:31:21 +00:00
refactor: timesheet
This commit is contained in:
@@ -37,7 +37,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=1)
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
|
||||
|
||||
self.assertEqual(timesheet.total_hours, 2)
|
||||
self.assertEqual(timesheet.total_billable_hours, 2)
|
||||
@@ -49,7 +49,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=0)
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=0)
|
||||
|
||||
self.assertEqual(timesheet.total_hours, 2)
|
||||
self.assertEqual(timesheet.total_billable_hours, 0)
|
||||
@@ -61,7 +61,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||
|
||||
salary_structure = make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate = True, billable=1)
|
||||
timesheet = make_timesheet(emp, simulate = True, is_billable=1)
|
||||
salary_slip = make_salary_slip(timesheet.name)
|
||||
salary_slip.submit()
|
||||
|
||||
@@ -82,7 +82,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
def test_sales_invoice_from_timesheet(self):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=1)
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
|
||||
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
|
||||
sales_invoice.due_date = nowdate()
|
||||
sales_invoice.submit()
|
||||
@@ -100,7 +100,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company')
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=1, project=project, company='_Test Company')
|
||||
sales_invoice = create_sales_invoice(do_not_save=True)
|
||||
sales_invoice.project = project
|
||||
sales_invoice.submit()
|
||||
@@ -171,13 +171,13 @@ def make_salary_structure_for_timesheet(employee, company=None):
|
||||
return salary_structure
|
||||
|
||||
|
||||
def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None):
|
||||
def make_timesheet(employee, simulate=False, is_billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None):
|
||||
update_activity_type(activity_type)
|
||||
timesheet = frappe.new_doc("Timesheet")
|
||||
timesheet.employee = employee
|
||||
timesheet.company = company or '_Test Company'
|
||||
timesheet_detail = timesheet.append('time_logs', {})
|
||||
timesheet_detail.billable = billable
|
||||
timesheet_detail.is_billable = is_billable
|
||||
timesheet_detail.activity_type = activity_type
|
||||
timesheet_detail.from_time = now_datetime()
|
||||
timesheet_detail.hours = 2
|
||||
|
||||
@@ -90,17 +90,50 @@ frappe.ui.form.on("Timesheet", {
|
||||
}
|
||||
if(frm.doc.per_billed > 0) {
|
||||
frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false);
|
||||
frm.fields_dict["time_logs"].grid.toggle_enable("billable", false);
|
||||
frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false);
|
||||
}
|
||||
frm.trigger('setup_filters');
|
||||
},
|
||||
|
||||
customer: function(frm) {
|
||||
frm.set_query('parent_project', function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"customer": doc.customer
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('project', 'time_logs', function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"customer": doc.customer
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
make_invoice: function(frm) {
|
||||
let fields = [{
|
||||
"fieldtype": "Link",
|
||||
"label": __("Item Code"),
|
||||
"fieldname": "item_code",
|
||||
"options": "Item"
|
||||
}]
|
||||
|
||||
if (!frm.doc.customer) {
|
||||
fields.push({
|
||||
"fieldtype": "Link",
|
||||
"label": __("Customer"),
|
||||
"fieldname": "customer",
|
||||
"options": "Customer",
|
||||
"default": frm.doc.customer
|
||||
});
|
||||
}
|
||||
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Item (optional)"),
|
||||
fields: [
|
||||
{"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"},
|
||||
{"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"}
|
||||
]
|
||||
title: __("Create Sales Invoice"),
|
||||
fields: fields
|
||||
});
|
||||
|
||||
dialog.set_primary_action(__('Create Sales Invoice'), () => {
|
||||
@@ -113,7 +146,8 @@ frappe.ui.form.on("Timesheet", {
|
||||
args: {
|
||||
"source_name": frm.doc.name,
|
||||
"item_code": args.item_code,
|
||||
"customer": args.customer
|
||||
"customer": frm.doc.customer || args.customer,
|
||||
"currency": frm.doc.currency
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
@@ -136,8 +170,7 @@ frappe.ui.form.on("Timesheet", {
|
||||
|
||||
parent_project: function(frm) {
|
||||
set_project_in_timelog(frm);
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Timesheet Detail", {
|
||||
@@ -196,7 +229,7 @@ frappe.ui.form.on("Timesheet Detail", {
|
||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
billable: function(frm, cdt, cdn) {
|
||||
is_billable: function(frm, cdt, cdn) {
|
||||
update_billing_hours(frm, cdt, cdn);
|
||||
update_time_rates(frm, cdt, cdn);
|
||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||
@@ -239,9 +272,9 @@ var calculate_end_time = function(frm, cdt, cdn) {
|
||||
}
|
||||
};
|
||||
|
||||
var update_billing_hours = function(frm, cdt, cdn){
|
||||
var child = locals[cdt][cdn];
|
||||
if(!child.billable) {
|
||||
var update_billing_hours = function(frm, cdt, cdn) {
|
||||
let child = frappe.get_doc(cdt, cdn);
|
||||
if (!child.is_billable) {
|
||||
frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
|
||||
} else {
|
||||
// bill all hours by default
|
||||
@@ -249,19 +282,19 @@ var update_billing_hours = function(frm, cdt, cdn){
|
||||
}
|
||||
};
|
||||
|
||||
var update_time_rates = function(frm, cdt, cdn){
|
||||
var child = locals[cdt][cdn];
|
||||
if(!child.billable){
|
||||
var update_time_rates = function(frm, cdt, cdn) {
|
||||
let child = frappe.get_doc(cdt, cdn);
|
||||
if (!child.is_billable) {
|
||||
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
|
||||
}
|
||||
};
|
||||
|
||||
var calculate_billing_costing_amount = function(frm, cdt, cdn){
|
||||
var child = locals[cdt][cdn];
|
||||
var billing_amount = 0.0;
|
||||
var costing_amount = 0.0;
|
||||
var calculate_billing_costing_amount = function(frm, cdt, cdn) {
|
||||
let child = frappe.get_doc(cdt, cdn);
|
||||
let billing_amount = 0.0;
|
||||
let costing_amount = 0.0;
|
||||
|
||||
if(child.billing_hours && child.billable){
|
||||
if (child.billing_hours && child.is_billable) {
|
||||
billing_amount = (child.billing_hours * child.billing_rate);
|
||||
}
|
||||
costing_amount = flt(child.costing_rate * child.hours);
|
||||
@@ -271,18 +304,18 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){
|
||||
};
|
||||
|
||||
var calculate_time_and_amount = function(frm) {
|
||||
var tl = frm.doc.time_logs || [];
|
||||
var total_working_hr = 0;
|
||||
var total_billing_hr = 0;
|
||||
var total_billable_amount = 0;
|
||||
var total_costing_amount = 0;
|
||||
let tl = frm.doc.time_logs || [];
|
||||
let total_working_hr = 0;
|
||||
let total_billing_hr = 0;
|
||||
let total_billable_amount = 0;
|
||||
let total_costing_amount = 0;
|
||||
for(var i=0; i<tl.length; i++) {
|
||||
if (tl[i].hours) {
|
||||
total_working_hr += tl[i].hours;
|
||||
total_billable_amount += tl[i].billing_amount;
|
||||
total_costing_amount += tl[i].costing_amount;
|
||||
|
||||
if(tl[i].billable){
|
||||
if (tl[i].is_billable) {
|
||||
total_billing_hr += tl[i].billing_hours;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"title",
|
||||
"naming_series",
|
||||
"company",
|
||||
"customer",
|
||||
"currency",
|
||||
"sales_invoice",
|
||||
"column_break_3",
|
||||
"salary_slip",
|
||||
@@ -176,7 +178,6 @@
|
||||
"default": "0",
|
||||
"fieldname": "total_hours",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Working Hours",
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -199,7 +200,6 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "total_billed_hours",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Billed Hours",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
@@ -209,6 +209,7 @@
|
||||
"fieldname": "total_costing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Costing Amount",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -222,6 +223,7 @@
|
||||
"fieldname": "total_billable_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Billable Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -229,6 +231,7 @@
|
||||
"fieldname": "total_billed_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Billed Amount",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -236,6 +239,7 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "per_billed",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Amount Billed",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
@@ -265,13 +269,27 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"fetch_from": "customer.default_currency",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-clock-o",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-08 20:51:14.590080",
|
||||
"modified": "2021-05-13 17:13:29.954960",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Timesheet",
|
||||
|
||||
@@ -47,7 +47,7 @@ class Timesheet(Document):
|
||||
|
||||
self.total_hours += flt(d.hours)
|
||||
self.total_costing_amount += flt(d.costing_amount)
|
||||
if d.billable:
|
||||
if d.is_billable:
|
||||
self.total_billable_hours += flt(d.billing_hours)
|
||||
self.total_billable_amount += flt(d.billing_amount)
|
||||
self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0
|
||||
@@ -59,7 +59,7 @@ class Timesheet(Document):
|
||||
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
|
||||
|
||||
def update_billing_hours(self, args):
|
||||
if args.billable:
|
||||
if args.is_billable:
|
||||
if flt(args.billing_hours) == 0.0:
|
||||
args.billing_hours = args.hours
|
||||
else:
|
||||
@@ -133,16 +133,20 @@ class Timesheet(Document):
|
||||
def validate_time_logs(self):
|
||||
for data in self.get('time_logs'):
|
||||
self.validate_overlap(data)
|
||||
self.validate_task_project()
|
||||
self.validate_task_project(data)
|
||||
self.validate_project(data)
|
||||
|
||||
def validate_overlap(self, data):
|
||||
settings = frappe.get_single('Projects Settings')
|
||||
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
|
||||
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
|
||||
|
||||
def validate_task_project(self):
|
||||
for log in self.time_logs:
|
||||
log.project = log.project or frappe.db.get_value("Task", log.task, "project")
|
||||
def validate_task_project(self, data):
|
||||
data.project = data.project or frappe.db.get_value("Task", data.task, "project")
|
||||
|
||||
def validate_project(self, data):
|
||||
if self.parent_project != data.project:
|
||||
frappe.throw(_("Row {0}: Poject must be same as {1}.")).format(data.idx, self.parent_project)
|
||||
|
||||
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
||||
if not value or ignore_validation:
|
||||
@@ -189,7 +193,7 @@ class Timesheet(Document):
|
||||
|
||||
def update_cost(self):
|
||||
for data in self.time_logs:
|
||||
if data.activity_type or data.billable:
|
||||
if data.activity_type or data.is_billable:
|
||||
rate = get_activity_cost(self.employee, data.activity_type)
|
||||
hours = data.billing_hours or 0
|
||||
costing_hours = data.billing_hours or data.hours or 0
|
||||
@@ -200,20 +204,31 @@ class Timesheet(Document):
|
||||
data.costing_amount = data.costing_rate * costing_hours
|
||||
|
||||
def update_time_rates(self, ts_detail):
|
||||
if not ts_detail.billable:
|
||||
if not ts_detail.is_billable:
|
||||
ts_detail.billing_rate = 0.0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None):
|
||||
condition = ''
|
||||
def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None, currency=None):
|
||||
condition = field = join = ''
|
||||
if parent:
|
||||
condition = "AND parent = %(parent)s"
|
||||
condition = "AND tsd.parent = %(parent)s"
|
||||
if from_time and to_time:
|
||||
condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
||||
condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
||||
if currency:
|
||||
field = ", ts.currency as currency"
|
||||
join = " INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent "
|
||||
condition += " AND ts.currency = %(currency)s"
|
||||
|
||||
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
|
||||
from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
|
||||
and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
|
||||
return frappe.db.sql("""SELECT tsd.name as name,
|
||||
tsd.parent as parent, tsd.billing_hours as billing_hours,
|
||||
tsd.billing_amount as billing_amount, tsd.activity_type as activity_type,
|
||||
tsd.description as description {0}
|
||||
FROM `tabTimesheet Detail` tsd
|
||||
{1} where tsd.parenttype = 'Timesheet'
|
||||
and tsd.docstatus=1
|
||||
and tsd.project = %(project)s {2}
|
||||
and tsd.is_billable = 1
|
||||
and tsd.sales_invoice is null""".format(field, join, condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time, 'currency': currency}, as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -250,7 +265,7 @@ def get_timesheet_data(name, project):
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, item_code=None, customer=None):
|
||||
def make_sales_invoice(source_name, item_code=None, customer=None, currency=None):
|
||||
target = frappe.new_doc("Sales Invoice")
|
||||
timesheet = frappe.get_doc('Timesheet', source_name)
|
||||
|
||||
@@ -268,6 +283,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None):
|
||||
if customer:
|
||||
target.customer = customer
|
||||
|
||||
if currency:
|
||||
target.currency = currency
|
||||
|
||||
if item_code:
|
||||
target.append('items', {
|
||||
'item_code': item_code,
|
||||
@@ -275,11 +293,16 @@ def make_sales_invoice(source_name, item_code=None, customer=None):
|
||||
'rate': billing_rate
|
||||
})
|
||||
|
||||
target.append('timesheets', {
|
||||
'time_sheet': timesheet.name,
|
||||
'billing_hours': hours,
|
||||
'billing_amount': billing_amount
|
||||
})
|
||||
for time_log in timesheet.time_logs:
|
||||
if time_log.is_billable:
|
||||
target.append('timesheets', {
|
||||
'time_sheet': timesheet.name,
|
||||
'billing_hours': time_log.billing_hours,
|
||||
'billing_amount': time_log.billing_amount,
|
||||
'timesheet_detail': time_log.name,
|
||||
'activity_type': time_log.activity_type,
|
||||
'description': time_log.description
|
||||
})
|
||||
|
||||
target.run_method("calculate_billing_amount_for_timesheet")
|
||||
target.run_method("set_missing_values")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -126,7 +126,7 @@ def get_timesheet_details(filters, timesheet_list):
|
||||
timesheet_details = frappe.get_all(
|
||||
"Timesheet Detail",
|
||||
filters = timesheet_details_filter,
|
||||
fields=["from_time", "to_time", "hours", "billable", "billing_hours", "billing_rate", "parent"]
|
||||
fields=["from_time", "to_time", "hours", "is_billable", "billing_hours", "billing_rate", "parent"]
|
||||
)
|
||||
|
||||
timesheet_details_map = frappe._dict()
|
||||
@@ -139,7 +139,7 @@ def get_billable_and_total_duration(activity, start_time, end_time):
|
||||
precision = frappe.get_precision("Timesheet Detail", "hours")
|
||||
activity_duration = time_diff_in_hours(end_time, start_time)
|
||||
billing_duration = 0.0
|
||||
if activity.billable:
|
||||
if activity.is_billable:
|
||||
billing_duration = activity.billing_hours
|
||||
if activity_duration != activity.billing_hours:
|
||||
billing_duration = activity_duration * activity.billing_hours / activity.hours
|
||||
|
||||
@@ -140,7 +140,7 @@ class EmployeeHoursReport:
|
||||
additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'"
|
||||
|
||||
self.filtered_time_logs = frappe.db.sql('''
|
||||
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project
|
||||
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.is_billable AS is_billable, ttd.project AS project
|
||||
FROM `tabTimesheet Detail` AS ttd
|
||||
JOIN `tabTimesheet` AS tt
|
||||
ON ttd.parent = tt.name
|
||||
@@ -153,14 +153,14 @@ class EmployeeHoursReport:
|
||||
def generate_stats_by_employee(self):
|
||||
self.stats_by_employee = frappe._dict()
|
||||
|
||||
for emp, hours, billable, project in self.filtered_time_logs:
|
||||
for emp, hours, is_billable, project in self.filtered_time_logs:
|
||||
self.stats_by_employee.setdefault(
|
||||
emp, frappe._dict()
|
||||
).setdefault('billed_hours', 0.0)
|
||||
|
||||
self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0)
|
||||
|
||||
if billable:
|
||||
if is_billable:
|
||||
self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2)
|
||||
else:
|
||||
self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2)
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestEmployeeUtilization(unittest.TestCase):
|
||||
timesheet1.append("time_logs", {
|
||||
"activity_type": get_random("Activity Type"),
|
||||
"hours": 5,
|
||||
"billable": 1,
|
||||
"is_billable": 1,
|
||||
"from_time": '2021-04-01 13:30:00.000000',
|
||||
"to_time": '2021-04-01 18:30:00.000000'
|
||||
})
|
||||
@@ -46,7 +46,7 @@ class TestEmployeeUtilization(unittest.TestCase):
|
||||
timesheet2.append("time_logs", {
|
||||
"activity_type": get_random("Activity Type"),
|
||||
"hours": 10,
|
||||
"billable": 0,
|
||||
"is_billable": 0,
|
||||
"from_time": '2021-04-01 13:30:00.000000',
|
||||
"to_time": '2021-04-01 23:30:00.000000',
|
||||
"project": cls.test_project.name
|
||||
|
||||
@@ -14,7 +14,7 @@ class TestProjectProfitability(unittest.TestCase):
|
||||
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
|
||||
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
|
||||
make_salary_structure_for_timesheet(emp, company='_Test Company')
|
||||
self.timesheet = make_timesheet(emp, simulate = True, billable=1)
|
||||
self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
|
||||
self.salary_slip = make_salary_slip(self.timesheet.name)
|
||||
self.salary_slip.submit()
|
||||
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
|
||||
|
||||
Reference in New Issue
Block a user