Merge branch 'master' into develop
diff --git a/.eslintrc b/.eslintrc
index c9cd552..4dd1216 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -132,6 +132,7 @@
"get_url_arg": true,
"get_server_fields": true,
"set_multiple": true,
- "QUnit": true
+ "QUnit": true,
+ "Chart": true
}
}
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
index f6015f3..018d368 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
@@ -851,7 +851,7 @@
"4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
"44571-TVA collect\u00e9e": {
"account_type": "Tax",
- "tax_rate": 20.0
+ "is_group": 1
},
"44578-Taxes assimil\u00e9es \u00e0 la TVA": {}
},
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 790003c..cc35652 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -566,17 +566,26 @@
account = get_bank_cash_account(mode_of_payment, company).get("account")
if not account:
+ '''
+ Set the default account first. If the user hasn't set any default account then, he doesn't
+ want us to set any random account. In this case set the account only if there is single
+ account (of that type), otherwise return empty dict.
+ '''
if account_type=="Bank":
account = frappe.db.get_value("Company", company, "default_bank_account")
if not account:
- account = frappe.db.get_value("Account",
- {"company": company, "account_type": "Bank", "is_group": 0})
+ account_list = frappe.get_all("Account", filters = {"company": company,
+ "account_type": "Bank", "is_group": 0})
+ if len(account_list) == 1:
+ account = account_list[0].name
elif account_type=="Cash":
account = frappe.db.get_value("Company", company, "default_cash_account")
if not account:
- account = frappe.db.get_value("Account",
- {"company": company, "account_type": "Cash", "is_group": 0})
+ account_list = frappe.get_all("Account", filters = {"company": company,
+ "account_type": "Cash", "is_group": 0})
+ if len(account_list) == 1:
+ account = account_list[0].name
if account:
account_details = frappe.db.get_value("Account", account,
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 56db392..ba5b7f2 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -309,14 +309,16 @@
rows = []
for d in data:
- rows.append(d[self.ageing_col_idx_start : self.ageing_col_idx_start+4])
-
- if rows:
- rows.insert(0, [[d.get("label")] for d in ageing_columns])
+ rows.append(
+ {
+ 'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+4]
+ }
+ )
return {
"data": {
- 'labels': rows
+ 'labels': [d.get("label") for d in ageing_columns],
+ 'datasets': rows
},
"type": 'percentage'
}
diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py
index a8514b2..b97e097 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -15,6 +15,7 @@
{
"type": "doctype",
"name": "Task",
+ "route": "Tree/Task",
"description": _("Project activity / task."),
},
{
diff --git a/erpnext/docs/user/manual/en/customize-erpnext/articles/field-types.md b/erpnext/docs/user/manual/en/customize-erpnext/articles/field-types.md
index 24cb4ab..c73ebc7 100644
--- a/erpnext/docs/user/manual/en/customize-erpnext/articles/field-types.md
+++ b/erpnext/docs/user/manual/en/customize-erpnext/articles/field-types.md
@@ -56,6 +56,17 @@
Link field is connected to another master from where it fetches data. For example, in the Quotation master, Customer is a Link field.
+- Geolocation
+
+Use Geolocation field to store GeoJSON <a href="https://tools.ietf.org/html/rfc7946#section-3.3">featurecollection</a>. Stores polygons, lines and points. Internally it uses following custom properties for identifying a circle.
+
+```
+{
+ "point_type": "circle",
+ "radius": 10.00
+}
+```
+
- Password
Password field will have decode value in it.
@@ -84,4 +95,4 @@
Text Editor is text field. It has text-formatting options. In ERPNext, this field is generally used for defining Terms and Conditions.
-<!-- markdown -->
\ No newline at end of file
+<!-- markdown -->
diff --git a/erpnext/docs/user/manual/es/index.txt b/erpnext/docs/user/manual/es/index.txt
index feb68a2..00cf97b 100644
--- a/erpnext/docs/user/manual/es/index.txt
+++ b/erpnext/docs/user/manual/es/index.txt
@@ -1,3 +1,4 @@
introduction
accounts
+projects
schools
diff --git a/erpnext/docs/user/manual/es/projects/__init__.py b/erpnext/docs/user/manual/es/projects/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/__init__.py
diff --git a/erpnext/docs/user/manual/es/projects/activity-cost.md b/erpnext/docs/user/manual/es/projects/activity-cost.md
new file mode 100644
index 0000000..71eb15e
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/activity-cost.md
@@ -0,0 +1,6 @@
+# Costo de Actividad
+
+El costo de la actividad registra la tasa de facturación por hora y la tasa de costos de un empleado en comparación con un tipo de actividad.
+El sistema hace uso de esta tasa mientras hace registros de tiempo. Se usa para Costeo de proyectos.
+
+<img class="screenshot" alt="Activity Cost" src="/docs/assets/img/project/activity_cost.png">
diff --git a/erpnext/docs/user/manual/es/projects/activity-type.md b/erpnext/docs/user/manual/es/projects/activity-type.md
new file mode 100644
index 0000000..20a03e2
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/activity-type.md
@@ -0,0 +1,15 @@
+# Tipo de Actividad
+
+Los tipos de actividad son la lista de los diferentes tipos de actividades sobre las que se hacen registro de tiempo.
+
+<img class="screenshot" alt="Activity Type" src="/docs/assets/img/project/activity_type.png">
+
+Por defecto, los siguientes tipos de actividades son creados.
+
+* Planning
+* Research
+* Proposal Writing
+* Execution
+* Communication
+
+{next}
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/articles/__init__.py b/erpnext/docs/user/manual/es/projects/articles/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/articles/__init__.py
diff --git a/erpnext/docs/user/manual/es/projects/articles/index.md b/erpnext/docs/user/manual/es/projects/articles/index.md
new file mode 100644
index 0000000..2d959ec
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/articles/index.md
@@ -0,0 +1,3 @@
+# Artículos
+
+{index}
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/articles/index.txt b/erpnext/docs/user/manual/es/projects/articles/index.txt
new file mode 100644
index 0000000..56c193c
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/articles/index.txt
@@ -0,0 +1 @@
+project-costing
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/articles/project-costing.md b/erpnext/docs/user/manual/es/projects/articles/project-costing.md
new file mode 100644
index 0000000..a8820c7
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/articles/project-costing.md
@@ -0,0 +1,40 @@
+# Costeo de proyectos
+
+Cada proyecto tiene multiples tareas asociadas a el. Para hacer el seguimiento del costo actual de un proyecto, primeramente en términos de servicios, el usuario
+tiene que crear un registro de tiempo basado en el tiempo que invirtió en una tarea del proyecto. Siguiendo los pasos de como puedes hacer el seguimiento del costo actual de un servicio usando el proyecto.
+
+#### Tipo de actividad
+
+Tipo de actividad es un maestro de los servicios ofrecidos por su personal. Puedes agregar un nuevo Tipo de Actividad desde:
+
+`Project > Activity Type > New`
+
+#### Costo de actividad
+
+Costo de actividad es un maestro donde puedes hacer el seguimiento de los montos de facturación y costo de cada empleado, y por cada tipo de Tipo de Actividad.
+
+<img alt="Activity Cost" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.57.01 pm.png">
+
+#### Registro de Tiempo
+
+Basados en el tiempo actual invertido en una Tarea del Proyecto, El empleado va a crear un registro de tiempo.
+
+<img alt="Time Log" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.59.49 pm.png">
+
+Al momento de seleccionar el Tipo de Actividad en el Registro de tiempo, el monto de Facturación y Costo del empleado va a ser traído de su respectivo registro en el master de Costo de Actividad.
+
+<img alt="Time Log Costing" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.00.06 pm.png">
+
+Multiplicando esos montos con el total de número de horas en el registro de tiempo, nos da el monto de costos y Facturación para el registro de tiempo específico.
+
+#### Costeo en Proyectos y Tareas
+
+Basados en el total de registros de tiempos creados por una tarea en específico, su costo va a ser actualizado en el registro maestro de la tarea, o sea, en el detalle de la tarea.
+
+<img alt="Costing in Task" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.54 pm.png">
+
+De la misma manera, el detalle del Proyecto va a actualizar su costo basado en el total de registros de tiempo a ese proyecto, y las tareas asociadas a ese proyecto.
+
+<img alt="Costing in Project" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.29 pm.png">
+
+<!-- markdown -->
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/index.md b/erpnext/docs/user/manual/es/projects/index.md
new file mode 100644
index 0000000..0882752
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/index.md
@@ -0,0 +1,15 @@
+# Proyectos
+
+ERPNext le ayuda en la administración de su proyecto a traves de la creacion de tareas y
+poder asignarlas a diferentes personas.
+
+Las compras y las ventas también se pueden rastrear en relación con los proyectos y
+esto puede ayudar a la empresa a controlar su presupuesto, entrega y rentabilidad para un proyecto.
+
+Los proyectos pueden ser usados para manejar los proyectos internos, trabajos de manufacturación y
+planificación de servicios. Para los trabajos de servicios, los Time Sheets (hojas de tiempo) pueden ser creadas
+para facturar a los clientes, en caso que el proceso de facturación se haga basado en tiempo y dinero de tareas.
+
+### Temas
+
+{index}
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/index.txt b/erpnext/docs/user/manual/es/projects/index.txt
new file mode 100644
index 0000000..716ec1fe
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/index.txt
@@ -0,0 +1,7 @@
+tasks
+project
+time-log-batch
+activity-type
+activity-cost
+articles
+timesheet
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/projects/project.md b/erpnext/docs/user/manual/es/projects/project.md
new file mode 100644
index 0000000..942433b
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/project.md
@@ -0,0 +1,110 @@
+# Proyecto
+
+El manejo de proyectos en ERPNext se hace a traves de tareas. Puedes crear un proyecto y asignar varias tareas al mismo.
+
+<img class="screenshot" alt="Project" src="/docs/assets/img/project/project.png">
+
+También puedes hacer el seguimiento del % completado del proyecto usando diferentes métodos.
+
+ 1. Tareas Completadas
+ 2. Progreso de tareas
+ 3. Peso de tarea
+
+<img class="screenshot" alt="Project" src="/docs/assets/img/project/project-percent-complete.png">
+
+Algunos ejemplos de como el % completado es cálculado basado en tareas.
+
+<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-calc.png">
+
+<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-formula.png">
+
+### Manejando tareas
+
+Los proyecto pueden ser divididos en multiples tareas.
+Las tareas pueden ser creadas a traves del documento de Proyecto o pueden ser creadas via [Tarea](/docs/user/manual/en/projects/tasks.html)
+
+<img class="screenshot" alt="Project" src="/docs/assets/img/project/project_task.png">
+
+* Para ver las tareas creadas a un proyecto click en 'Tasks'
+
+<img class="screenshot" alt="Project - View Task" src="/docs/assets/img/project/project_view_task.png">
+
+<img class="screenshot" alt="Project - Task List" src="/docs/assets/img/project/project_task_list.png">
+
+* También puedes ver las tareas desde la misma vista del proyecto.
+
+<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/project_task_grid.png">
+
+* Para agregar peso a las tareas puedes seguir los pasos siguientes
+
+<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/tasks.png">
+<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/task-weights.png">
+
+
+### Manejando tiempo
+
+ERPNext usa [Time Log](/docs/user/manual/en/projects/time-log.html) para hacer el seguimiento del progreso de un Proyecto.
+Puedes crear registros de tiempo sobre cada Tarea.
+El tiempo actual de inicio y finalización junto con el costo deben ser actualizados basados en los Registros de Tiempo.
+
+* Para ver los Registros de Tiempo realizados a un proyecto, dar click en 'Time Logs'
+
+<img class="screenshot" alt="Project - View Time Log" src="/docs/assets/img/project/project_view_time_log.png">
+
+<img class="screenshot" alt="Project - Time Log List" src="/docs/assets/img/project/project_time_log_list.png">
+
+* Puedes agregar un registro de tiempo directamente y luego asociarlo con el proyecto.
+
+<img class="screenshot" alt="Project - Link Time Log" src="/docs/assets/img/project/project_time_log_link.png">
+
+### Gestión de gastos
+
+Puede reservar la [Reclamación de gastos](/docs/user/manual/en/human-resources/expense-claim.html) contra una tarea de proyecto.
+El sistema actualizará el monto total de las reclamaciones de gastos en la sección de costos del proyecto.
+
+* Para ver las reclamaciones de gastos realizadas en un proyecto, haga clic en 'Reclamaciones de gastos'
+
+<img class="screenshot" alt="Project - View Expense Claim" src="/docs/assets/img/project/project_view_expense_claim.png">
+
+* También puede crear un Reclamo de gastos directamente y vincularlo al Proyecto.
+
+<img class="screenshot" alt="Project - Link Expense Claim" src="/docs/assets/img/project/project_expense_claim_link.png">
+
+* El monto total de los Reclamos de gastos reservados contra un proyecto se muestra en 'Reclamo de gastos totales' en la Sección de Costos del proyecto
+
+<img class="screenshot" alt="Project - Total Expense Claim" src="/docs/assets/img/project/project_total_expense_claim.png">
+
+### Centro de Costo
+
+Puedes crear un [Cost Center](/docs/user/manual/en/accounts/setup/cost-center.html) sobre un proyecto o usar un centro de costo existente para hacer el seguimiento de todos los gastos realizados al proyecto.
+
+<img class="screenshot" alt="Project - Cost Center" src="/docs/assets/img/project/project_cost_center.png">
+
+###Costeo del proyecto
+
+La sección Costeo del proyecto le ayuda a rastrear el tiempo y los gastos incurridos en relación con el proyecto.
+
+<img class="screenshot" alt="Project - Costing" src="/docs/assets/img/project/project_costing.png">
+
+* La sección de cálculo de costos se actualiza según los registros de tiempo realizados.
+
+* El margen bruto es la diferencia entre el monto total de costos y el monto total de facturación
+
+###Facturación
+
+Puedes crear/enlazar una [Sales Order](/docs/user/manual/en/selling/sales-order.html) a un proyecto. Una vez asociada puedes usar el módulo de ventas para facturar a un cliente sobre el proyecto.
+
+<img class="screenshot" alt="Project - Sales Order" src="/docs/assets/img/project/project_sales_order.png">
+
+###Gantt Chart
+
+Un Gantt Chart muestra la planificación del proyecto.
+ERPNext te provee con una vista para visualizar las tareas de forma calendarizada usando un Gantt Chart (Hoja de Gantt).
+
+* Para visualizar el gantt chart de un proyecto, ve hasta el proyecto y dar click en 'Gantt Chart'
+
+<img class="screenshot" alt="Project - View Gantt Chart" src="/docs/assets/img/project/project_view_gantt_chart.png">
+
+<img class="screenshot" alt="Project - Gantt Chart" src="/docs/assets/img/project/project_gantt_chart.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/projects/tasks.md b/erpnext/docs/user/manual/es/projects/tasks.md
new file mode 100644
index 0000000..b07b305
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/tasks.md
@@ -0,0 +1,61 @@
+# Tareas
+
+Proyecto es dividido en Tareas.
+En ERPNext, puedes crear las tareas de forma independiente.
+
+<img class="screenshot" alt="Task" src="/docs/assets/img/project/task.png">
+
+### Estado de una Tarea
+
+Una tarea puede tener uno de los siguientes estados - Abierto, Trabajando, Pendiente de Revisión, Cerrado, o Cancelado.
+
+<img class="screenshot" alt="Task - Status" src="/docs/assets/img/project/task_status.png">
+
+* Por defecto, cada nueva tarea creada se le establece el estado 'Abierto'.
+
+* Si un registro de tiempo es realizado sobre una tarea, su estado es asignado a 'Working'.
+
+### Tarea Dependiente
+
+Puedes especificar una lista de tareas dependientes en la sección 'Depende de'
+
+<img class="screenshot" alt="Depends On" src="/docs/assets/img/project/task_depends_on.png">
+
+* No puedes cerrar una tarea padre hasta que todas las tareas dependientes esten cerradas.
+
+* Si una tarea dependiente se encuentra en retraso y se sobrepone con la fecha esperada de inicio de la tarea padre, el sistema va a re calandarizar la tarea padre.
+
+### Manejando el tiempo
+
+ERPNext usa [Time Log](/docs/user/manual/en/projects/time-log.html) para seguir el progreso de una tarea.
+Puedes crear varios registros de tiempo para cada tarea.
+El tiempo de inicio y fin actual junto con el costo es actualizado en base al Registro de Tiempo.
+
+* Para ver el Registro de tiempo realizado a una tarea, dar click en 'Time Logs'
+
+<img class="screenshot" alt="Task - View Time Log" src="/docs/assets/img/project/task_view_time_log.png">
+
+<img class="screenshot" alt="Task - Time Log List" src="/docs/assets/img/project/task_time_log_list.png">
+
+* Puedes también crear un Registro de Tiempo directamente y luego asociarlo a una Tarea.
+
+<img class="screenshot" alt="Task - Link Time Log" src="/docs/assets/img/project/task_time_log_link.png">
+
+### Gestión de gastos
+
+Puede reservar la [Reclamación de gastos](/docs/user/manual/en/human-resources/expense-claim.html) contra una tarea de proyecto.
+El sistema actualizará el monto total de las reclamaciones de gastos en la sección de costos del proyecto.
+
+* Para ver las reclamaciones de gastos realizadas en un proyecto, haga clic en 'Reclamaciones de gastos'
+
+<img class="screenshot" alt="Task - View Expense Claim" src="/docs/assets/img/project/task_view_expense_claim.png">
+
+* También puede crear un Reclamo de gastos directamente y vincularlo al Proyecto.
+
+<img class="screenshot" alt="Task - Link Expense Claim" src="/docs/assets/img/project/task_expense_claim_link.png">
+
+* El monto total de los Reclamos de gastos reservados contra un proyecto se muestra en 'Reclamo de gastos totales' en la Sección de Costos del proyecto
+
+<img class="screenshot" alt="Task - Total Expense Claim" src="/docs/assets/img/project/task_total_expense_claim.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/projects/time-log-batch.md b/erpnext/docs/user/manual/es/projects/time-log-batch.md
new file mode 100644
index 0000000..72c77b4
--- /dev/null
+++ b/erpnext/docs/user/manual/es/projects/time-log-batch.md
@@ -0,0 +1,25 @@
+# Lote de registro de tiempo
+
+Puede facturar Registros de tiempo viéndolos juntos. Esto le da la flexibilidad de administrar la facturación de su cliente de la manera que desee. Para crear una nueva hoja de tiempo, ve a
+
+> Projects > Time Sheet > New Time Sheet
+
+O
+
+Simplemente abra su lista de registro de tiempo y marque los elementos que desea agregar al registro de tiempo. A continuación, haga clic en el botón "Crear hoja de tiempo" y se seleccionarán estos registros de tiempo.
+
+<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet.gif">
+
+###Creando Factura de Venta
+
+* Despues de crear la Hoja de Tiempo/Horario, el botón "Crear Factura" debe aparecer.
+
+<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet_make_invoice.png">
+
+* Haga clic en ese botón para hacer una factura de venta usando la hoja de tiempo.
+
+<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet_sales_invoice.png">
+
+* Cuando "Presente" la Factura de Ventas, el número de Factura de Ventas se actualizará en los Registros de Tiempo y la Hoja de Horario y su estado cambiará a "Facturado".
+
+{next}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/offer_letter/test_offer_letter.js b/erpnext/hr/doctype/offer_letter/test_offer_letter.js
index 5b61d64..c9b08a6 100644
--- a/erpnext/hr/doctype/offer_letter/test_offer_letter.js
+++ b/erpnext/hr/doctype/offer_letter/test_offer_letter.js
@@ -27,13 +27,13 @@
]},
]);
},
- () => frappe.timeout(12),
+ () => frappe.timeout(10),
() => frappe.click_button('Submit'),
() => frappe.timeout(2),
() => frappe.click_button('Yes'),
- () => frappe.timeout(8),
+ () => frappe.timeout(5),
+ // To check if the fields are correctly set
() => {
- // To check if the fields are correctly set
assert.ok(cur_frm.get_field('status').value=='Accepted',
'Status of job offer is correct');
assert.ok(cur_frm.get_field('designation').value=='Software Developer',
@@ -45,7 +45,7 @@
() => {
assert.ok(cur_list.data[0].docstatus==1,'Offer Letter Submitted successfully');
},
- () => frappe.timeout(4),
+ () => frappe.timeout(2),
() => done()
]);
});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/training_event/tests/test_training_event.js b/erpnext/hr/doctype/training_event/tests/test_training_event.js
index a359af3..8ff4fec 100644
--- a/erpnext/hr/doctype/training_event/tests/test_training_event.js
+++ b/erpnext/hr/doctype/training_event/tests/test_training_event.js
@@ -1,7 +1,7 @@
QUnit.module('hr');
QUnit.test("Test: Training Event [HR]", function (assert) {
- assert.expect(4);
+ assert.expect(5);
let done = assert.async();
let employee_name;
@@ -21,7 +21,8 @@
{ employees: [
[
{employee: employee_name},
- {employee_name: 'Test Employee 1'}
+ {employee_name: 'Test Employee 1'},
+ {attendance: 'Optional'}
]
]},
]);
@@ -41,6 +42,9 @@
assert.ok(cur_frm.doc.employees[0].employee_name=='Test Employee 1',
'Attendee Employee is correctly set');
+
+ assert.ok(cur_frm.doc.employees[0].attendance=='Optional',
+ 'Attendance is correctly set');
},
() => frappe.set_route('List','Training Event','List'),
diff --git a/erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js b/erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
deleted file mode 100644
index 6364308..0000000
--- a/erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
+++ /dev/null
@@ -1,40 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("test: Training Event", function (assert) {
- // number of asserts
- assert.expect(1);
- let done = assert.async();
-
- frappe.run_serially([
- // insert a new Training Event
- () => frappe.set_route("List", "Training Event", "List"),
- () => frappe.new_doc("Training Event"),
- () => frappe.timeout(1),
- () => frappe.click_link('Edit in full page'),
- () => cur_frm.set_value("event_name", "Test Event " + frappe.utils.get_random(10)),
- () => cur_frm.set_value("start_time", "2017-07-26, 2:00 pm PDT"),
- () => cur_frm.set_value("end_time", "2017-07-26, 2:30 pm PDT"),
- () => cur_frm.set_value("introduction", "This is a test report"),
- () => cur_frm.set_value("location", "Fake office"),
- () => frappe.click_button('Add Row'),
- () => frappe.db.get_value('Employee', {'employee_name':'Test Employee 1'}, 'name'),
- (r) => {
- console.log(r);
- return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.employee = r.message.name;
- },
- () => {
- return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.attendance = "Optional";
- },
- () => frappe.click_button('Save'),
- () => frappe.timeout(2),
- () => frappe.click_button('Submit'),
- () => frappe.timeout(2),
- () => frappe.click_button('Yes'),
- () => frappe.timeout(1),
- () => {
- assert.equal(cur_frm.doc.docstatus, 1);
- },
- () => done()
- ]);
-
-});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/training_event/training_event.json b/erpnext/hr/doctype/training_event/training_event.json
index c1e18d3..4b812a9 100644
--- a/erpnext/hr/doctype/training_event/training_event.json
+++ b/erpnext/hr/doctype/training_event/training_event.json
@@ -809,7 +809,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-10-18 11:22:20.143491",
+ "modified": "2017-10-23 06:13:29.065781",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Event",
@@ -837,7 +837,7 @@
"write": 1
}
],
- "quick_entry": 1,
+ "quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "event_name",
diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py
index b5d9f19..a314081 100644
--- a/erpnext/hr/doctype/training_program/training_program_dashboard.py
+++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py
@@ -5,7 +5,7 @@
'fieldname': 'training_program',
'transactions': [
{
- 'label': _('Training Event'),
+ 'label': _('Training Events'),
'items': ['Training Event']
},
]
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
index 8efbed8..76a7f6f 100644
--- a/erpnext/hub_node/__init__.py
+++ b/erpnext/hub_node/__init__.py
@@ -15,13 +15,16 @@
return hub_settings
@frappe.whitelist()
-def get_items(start=0, limit=20, category=None, order_by=None, text=None):
+def get_items(start=0, limit=20, category=None, order_by=None, company=None, text=None):
connection = get_connection()
filters = {
'hub_category': category,
}
if text:
filters.update({'item_name': ('like', '%' + text + '%')})
+ if company:
+ filters.update({'company_name': company})
+
response = connection.get_list('Hub Item',
limit_start=start, limit_page_length=limit,
filters=filters)
diff --git a/erpnext/hub_node/page/hub/hub.js b/erpnext/hub_node/page/hub/hub.js
index 297a4d1..15bd97d 100644
--- a/erpnext/hub_node/page/hub/hub.js
+++ b/erpnext/hub_node/page/hub/hub.js
@@ -215,7 +215,7 @@
.on('click', '.company-link a', function(e) {
e.preventDefault();
const company_name = $(this).attr('data-company-name');
- me.get_company_details(company_name);
+ frappe.set_route('hub', 'Company', company_name);
})
.on('click', '.breadcrumb li', function(e) {
e.preventDefault();
@@ -476,26 +476,34 @@
}
get_company_details(company_id) {
- // get from cache if exists
- let company_details = this.company_cache[company_id];
- if(this.company_cache[company_id]) {
- this.go_to_company_page(company_details);
- return;
- }
- frappe.call({
- method: 'erpnext.hub_node.get_company_details',
- args: {company_id: company_id}
- }).then((r) => {
- if (r.message) {
- const company_details = r.message.company_details;
- this.company_cache[company_id] = company_details;
- this.go_to_company_page(company_details)
+ this.company_cache = this.company_cache || {};
+
+ return new Promise(resolve => {
+ // get from cache if exists
+ let company_details = this.company_cache[company_id];
+ if(company_details) {
+ resolve(company_details);
+ return;
}
- });
+ frappe.call({
+ method: 'erpnext.hub_node.get_company_details',
+ args: {hub_sync_id: company_id}
+ }).then((r) => {
+ if (r.message) {
+ const company_details = r.message;
+ this.company_cache[company_id] = company_details;
+ resolve(company_details)
+ }
+ });
+ })
}
- go_to_company_page(company_details) {
- frappe.set_route('hub', 'Company', company_details.company_name);
+ go_to_company_page(company_id) {
+ this.get_company_details(company_id)
+ .then(this.show_company_page.bind(this));
+ }
+
+ show_company_page(company_details) {
this.$hub_main_section.empty();
let $company_page =
@@ -574,10 +582,10 @@
<h2>${ company_details.company_name }</h2>
</div>
<div class="company">
- <span class="">${ company_details.seller_city }</span>
+ <span class="">${ company_details.country }</span>
</div>
<div class="description">
- <span class="small">${ company_details.seller_description }</span>
+ <span class="small">${ company_details.site_name }</span>
</div>
</div>
@@ -836,7 +844,7 @@
</div>
</a>
<div class="company-link">
- <a data-company-name="${ item.company_id }" class="">${ item.company_name }</a>
+ <a data-company-name="${ item.company_name }" class="">${ item.company_name }</a>
</div>
<div>${ item.formatted_price ? item.formatted_price : ''}</div>
</div>
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js
index c3e6f20..226ebfc 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.js
+++ b/erpnext/manufacturing/doctype/production_order/production_order.js
@@ -17,6 +17,14 @@
}
});
+ frm.set_query("source_warehouse", function() {
+ return {
+ filters: {
+ 'company': frm.doc.company,
+ }
+ }
+ });
+
frm.set_query("source_warehouse", "required_items", function() {
return {
filters: {
diff --git a/erpnext/manufacturing/page/production_analytics/production_analytics.js b/erpnext/manufacturing/page/production_analytics/production_analytics.js
index 39168b7..efbd0a5 100644
--- a/erpnext/manufacturing/page/production_analytics/production_analytics.js
+++ b/erpnext/manufacturing/page/production_analytics/production_analytics.js
@@ -64,7 +64,7 @@
var chart_data = this.get_chart_data ? this.get_chart_data() : null;
- this.chart = new frappe.chart.FrappeChart({
+ this.chart = new Chart({
parent: ".chart",
data: chart_data,
type: 'line'
diff --git a/erpnext/patches/v9_0/copy_old_fees_field_data.py b/erpnext/patches/v9_0/copy_old_fees_field_data.py
index fb11ee5..c47137b 100644
--- a/erpnext/patches/v9_0/copy_old_fees_field_data.py
+++ b/erpnext/patches/v9_0/copy_old_fees_field_data.py
@@ -5,6 +5,7 @@
import frappe
def execute():
+ frappe.reload_doctype('Fees')
if "total_amount" not in frappe.db.get_table_columns("Fees"):
return
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index df38cfe..b8f324a 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -19,38 +19,47 @@
},
refresh: function(frm) {
- var doc = frm.doc;
- if(doc.__islocal) {
- if(!frm.doc.exp_end_date) {
- frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+ frm.fields_dict['parent_task'].get_query = function() {
+ return {
+ filters: {
+ "is_group": 1,
+ }
}
}
-
- if(!doc.__islocal) {
- if(frappe.model.can_read("Timesheet")) {
- frm.add_custom_button(__("Timesheet"), function() {
- frappe.route_options = {"project": doc.project, "task": doc.name}
- frappe.set_route("List", "Timesheet");
- }, __("View"), true);
- }
- if(frappe.model.can_read("Expense Claim")) {
- frm.add_custom_button(__("Expense Claims"), function() {
- frappe.route_options = {"project": doc.project, "task": doc.name}
- frappe.set_route("List", "Expense Claim");
- }, __("View"), true);
+ if(!frm.is_group){
+ var doc = frm.doc;
+ if(doc.__islocal) {
+ if(!frm.doc.exp_end_date) {
+ frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+ }
}
- if(frm.perm[0].write) {
- if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
- frm.add_custom_button(__("Close"), function() {
- frm.set_value("status", "Closed");
- frm.save();
- });
- } else {
- frm.add_custom_button(__("Reopen"), function() {
- frm.set_value("status", "Open");
- frm.save();
- });
+ if(!doc.__islocal) {
+ if(frappe.model.can_read("Timesheet")) {
+ frm.add_custom_button(__("Timesheet"), function() {
+ frappe.route_options = {"project": doc.project, "task": doc.name}
+ frappe.set_route("List", "Timesheet");
+ }, __("View"), true);
+ }
+ if(frappe.model.can_read("Expense Claim")) {
+ frm.add_custom_button(__("Expense Claims"), function() {
+ frappe.route_options = {"project": doc.project, "task": doc.name}
+ frappe.set_route("List", "Expense Claim");
+ }, __("View"), true);
+ }
+
+ if(frm.perm[0].write) {
+ if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
+ frm.add_custom_button(__("Close"), function() {
+ frm.set_value("status", "Closed");
+ frm.save();
+ });
+ } else {
+ frm.add_custom_button(__("Reopen"), function() {
+ frm.set_value("status", "Open");
+ frm.save();
+ });
+ }
}
}
}
@@ -71,6 +80,21 @@
}
},
+ is_group: function(frm) {
+ frappe.call({
+ method:"erpnext.projects.doctype.task.task.check_if_child_exists",
+ args: {
+ name: frm.doc.name
+ },
+ callback: function(r){
+ if(r.message){
+ frappe.msgprint(__('Cannot convert it to non-group. Child Tasks exist.'));
+ frm.reload_doc();
+ }
+ }
+ })
+ },
+
validate: function(frm) {
frm.doc.project && frappe.model.remove_from_locals("Project",
frm.doc.project);
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index e4ab5a7..41950a3 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -3,7 +3,7 @@
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
- "autoname": "TASK.#####",
+ "autoname": "field:subject",
"beta": 0,
"creation": "2013-01-29 19:25:50",
"custom": 0,
@@ -30,9 +30,8 @@
"label": "Subject",
"length": 0,
"no_copy": 0,
- "oldfieldname": "subject",
- "oldfieldtype": "Data",
"permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -78,6 +77,37 @@
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
+ "bold": 1,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "is_group",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Is Group",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -173,9 +203,42 @@
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
- "bold": 0,
+ "bold": 1,
"collapsible": 0,
"columns": 0,
+ "fieldname": "parent_task",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Parent Task",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Task",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 1,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "collapsible_depends_on": "",
+ "columns": 0,
+ "depends_on": "",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
@@ -205,6 +268,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "exp_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -237,6 +301,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "depends_on": "",
"description": "",
"fieldname": "expected_time",
"fieldtype": "Float",
@@ -269,6 +334,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "task_weight",
"fieldtype": "Float",
"hidden": 0,
@@ -328,6 +394,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "exp_end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -359,6 +426,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "progress",
"fieldtype": "Percent",
"hidden": 0,
@@ -389,6 +457,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "is_milestone",
"fieldtype": "Check",
"hidden": 0,
@@ -418,7 +487,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
@@ -449,6 +520,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -481,7 +553,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -512,6 +586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "depends_on",
"fieldtype": "Table",
"hidden": 0,
@@ -543,6 +618,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "depends_on_tasks",
"fieldtype": "Data",
"hidden": 1,
@@ -572,7 +648,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"description": "",
"fieldname": "actual",
"fieldtype": "Section Break",
@@ -606,6 +684,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "act_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -638,6 +717,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "depends_on": "",
"description": "",
"fieldname": "actual_time",
"fieldtype": "Float",
@@ -699,6 +779,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "act_end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -730,6 +811,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break_17",
"fieldtype": "Section Break",
"hidden": 0,
@@ -759,6 +841,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -791,6 +874,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_expense_claim",
"fieldtype": "Currency",
"hidden": 0,
@@ -851,6 +935,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -1025,6 +1110,96 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "lft",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "lft",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rgt",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "rgt",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "old_parent",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Old Parent",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"has_web_view": 0,
@@ -1039,7 +1214,7 @@
"istable": 0,
"max_attachments": 5,
"menu_index": 0,
- "modified": "2017-05-23 11:28:28.161600",
+ "modified": "2017-10-06 03:57:37.901446",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 52ae132..5937f97 100644
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -5,13 +5,14 @@
import frappe, json
from frappe.utils import getdate, date_diff, add_days, cstr
-from frappe import _
-
-from frappe.model.document import Document
+from frappe import _, throw
+from frappe.utils.nestedset import NestedSet, rebuild_tree
class CircularReferenceError(frappe.ValidationError): pass
-class Task(Document):
+class Task(NestedSet):
+ nsm_parent_field = 'parent_task'
+
def get_feed(self):
return '{0}: {1}'.format(_(self.status), self.subject)
@@ -59,11 +60,16 @@
depends_on_tasks += d.task + ","
self.depends_on_tasks = depends_on_tasks
+ def update_nsm_model(self):
+ frappe.utils.nestedset.update_nsm(self)
+
def on_update(self):
+ self.update_nsm_model()
self.check_recursion()
self.reschedule_dependent_tasks()
self.update_project()
self.unassign_todo()
+ rebuild_tree("Task", "parent_task")
def unassign_todo(self):
if self.status == "Closed" or self.status == "Cancelled":
@@ -128,6 +134,17 @@
if project_user:
return True
+ def on_trash(self):
+ if check_if_child_exists(self.name):
+ throw(_("Child Task exists for this Task. You can not delete this Task."))
+
+ self.update_nsm_model()
+
+@frappe.whitelist()
+def check_if_child_exists(name):
+ return frappe.db.sql("""select name from `tabTask`
+ where parent_task = %s""", name)
+
@frappe.whitelist()
def get_events(start, end, filters=None):
"""Returns events for Gantt / Calendar view rendering.
@@ -177,4 +194,48 @@
and exp_end_date < CURDATE()
and `status` not in ('Closed', 'Cancelled')""")
+@frappe.whitelist()
+def get_children():
+ doctype = frappe.local.form_dict.get('doctype')
+ parent_field = 'parent_' + doctype.lower().replace(' ', '_')
+ parent = frappe.form_dict.get("parent") or ""
+
+ if parent == "task":
+ parent = ""
+
+ tasks = frappe.db.sql("""select name as value,
+ is_group as expandable
+ from `tab{doctype}`
+ where docstatus < 2
+ and ifnull(`{parent_field}`,'') = %s
+ order by name""".format(doctype=frappe.db.escape(doctype),
+ parent_field=frappe.db.escape(parent_field)), (parent), as_dict=1)
+
+ # return tasks
+ return tasks
+
+@frappe.whitelist()
+def add_node():
+ from frappe.desk.treeview import make_tree_args
+ args = frappe.form_dict
+ args.update({
+ "name_field": "subject"
+ })
+ args = make_tree_args(**args)
+
+ if args.parent_task == 'task':
+ args.parent_task = None
+
+ frappe.get_doc(args).insert()
+
+@frappe.whitelist()
+def add_multiple_tasks(data, parent):
+ data = json.loads(data)['tasks']
+ tasks = data.split('\n')
+ new_doc = {'doctype': 'Task', 'parent_task': parent}
+
+ for d in tasks:
+ new_doc['subject'] = d
+ new_task = frappe.get_doc(new_doc)
+ new_task.insert()
diff --git a/erpnext/projects/doctype/task/task_tree.js b/erpnext/projects/doctype/task/task_tree.js
new file mode 100644
index 0000000..f11c34f
--- /dev/null
+++ b/erpnext/projects/doctype/task/task_tree.js
@@ -0,0 +1,59 @@
+frappe.provide("frappe.treeview_settings");
+
+frappe.treeview_settings['Task'] = {
+ get_tree_nodes: "erpnext.projects.doctype.task.task.get_children",
+ add_tree_node: "erpnext.projects.doctype.task.task.add_node",
+ filters: [
+ {
+ fieldname: "task",
+ fieldtype:"Link",
+ options: "Task",
+ label: __("Task"),
+ get_query: function(){
+ return {
+ filters: [["Task", 'is_group', '=', 1]]
+ };
+ }
+ }
+ ],
+ title: "Task",
+ breadcrumb: "Projects",
+ get_tree_root: false,
+ root_label: "task",
+ ignore_fields:["parent_task"],
+ get_label: function(node) {
+ return node.data.value;
+ },
+ onload: function(me){
+ me.make_tree();
+ me.set_root = true;
+ },
+ toolbar: [
+ {
+ label:__("Add Multiple"),
+ condition: function(node) {
+ return node.expandable;
+ },
+ click: function(node) {
+ var d = new frappe.ui.Dialog({
+ 'fields': [
+ {'fieldname': 'tasks', 'label': 'Tasks', 'fieldtype': 'Text'},
+ ],
+ primary_action: function(){
+ d.hide();
+ return frappe.call({
+ method: "erpnext.projects.doctype.task.task.add_multiple_tasks",
+ args: {
+ data: d.get_values(),
+ parent: node.data.value
+ },
+ callback: function() { }
+ });
+ }
+ });
+ d.show();
+ }
+ }
+ ],
+ extend_toolbar: true
+};
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.js b/erpnext/projects/doctype/task/tests/test_task.js
similarity index 100%
rename from erpnext/projects/doctype/task/test_task.js
rename to erpnext/projects/doctype/task/tests/test_task.js
diff --git a/erpnext/projects/doctype/task/tests/test_task_tree.js b/erpnext/projects/doctype/task/tests/test_task_tree.js
new file mode 100644
index 0000000..9cbcf85
--- /dev/null
+++ b/erpnext/projects/doctype/task/tests/test_task_tree.js
@@ -0,0 +1,99 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Task Tree", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(5);
+
+ frappe.run_serially([
+ // insert a new Task
+ () => frappe.set_route('Tree', 'Task'),
+ () => frappe.timeout(0.5),
+
+ // Checking adding child without selecting any Node
+ () => frappe.tests.click_button('New'),
+ () => frappe.timeout(0.5),
+ () => {assert.equal($(`.msgprint`).text(), "Select a group node first.", "Error message success");},
+ () => frappe.tests.click_button('Close'),
+ () => frappe.timeout(0.5),
+
+ // Creating child nodes
+ () => frappe.tests.click_link('task'),
+ () => frappe.map_group.make('Test-1'),
+ () => frappe.map_group.make('Test-2'),
+ () => frappe.map_group.make('Test-3', 1),
+ () => frappe.timeout(1),
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.map_group.make('Test-4', 0),
+
+ // Checking Edit button
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-1'),
+ () => frappe.tests.click_button('Edit'),
+ () => frappe.timeout(0.5),
+ () => {assert.deepEqual(frappe.get_route(), ["Form", "Task", "Test-1"], "Edit route checks");},
+
+ // Deleting child Node
+ () => frappe.set_route('Tree', 'Task'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-1'),
+ () => frappe.tests.click_button('Delete'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_button('Yes'),
+
+ // Deleting Group Node that has child nodes in it
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.tests.click_button('Delete'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(1),
+ () => {assert.equal(cur_dialog.title, 'Message', 'Error thrown correctly');},
+ () => frappe.tests.click_button('Close'),
+
+ // Renaming Child node
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-2'),
+ () => frappe.tests.click_button('Rename'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('new_name', 'Test-5'),
+ () => frappe.timeout(1.5),
+ () => cur_dialog.get_primary_btn().click(),
+ () => frappe.timeout(1),
+ () => {assert.equal($(`a:contains("Test-5"):visible`).length, 1, 'Rename successfull');},
+
+ // Add multiple child tasks
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Add Multiple'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('tasks', 'Test-6\nTest-7'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Submit'),
+ () => frappe.timeout(2),
+ () => frappe.click_button('Expand All'),
+ () => frappe.timeout(1),
+ () => {
+ let count = $(`a:contains("Test-6"):visible`).length + $(`a:contains("Test-7"):visible`).length;
+ assert.equal(count, 2, "Multiple Tasks added successfully");
+ },
+
+ () => done()
+ ]);
+});
+
+frappe.map_group = {
+ make:function(subject, is_group = 0){
+ return frappe.run_serially([
+ () => frappe.click_button('Add Child'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('is_group', is_group),
+ () => cur_dialog.set_value('subject', subject),
+ () => frappe.click_button('Create New'),
+ () => frappe.timeout(1.5)
+ ]);
+ }
+};
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 1ea5962..43f5705 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -191,7 +191,6 @@
var child = locals[cdt][cdn];
if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
- frappe.model.set_value(cdt, cdn, 'costing_rate', 0.0);
}
}
@@ -202,9 +201,8 @@
if(child.billing_hours && child.billable){
billing_amount = (child.billing_hours * child.billing_rate);
- costing_amount = flt(child.costing_rate * child.billing_hours);
}
-
+ costing_amount = flt(child.costing_rate * child.hours);
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm);
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index ad566d5..01552a5 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -49,10 +49,10 @@
self.update_time_rates(d)
self.total_hours += flt(d.hours)
+ self.total_costing_amount += flt(d.costing_amount)
if d.billable:
self.total_billable_hours += flt(d.billing_hours)
self.total_billable_amount += flt(d.billing_amount)
- self.total_costing_amount += flt(d.costing_amount)
self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0
self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0
@@ -265,19 +265,19 @@
def update_cost(self):
for data in self.time_logs:
- if data.activity_type and data.billable:
+ if data.activity_type or data.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
if rate:
data.billing_rate = flt(rate.get('billing_rate')) if flt(data.billing_rate) == 0 else data.billing_rate
data.costing_rate = flt(rate.get('costing_rate')) if flt(data.costing_rate) == 0 else data.costing_rate
data.billing_amount = data.billing_rate * hours
- data.costing_amount = data.costing_rate * hours
+ data.costing_amount = data.costing_rate * costing_hours
def update_time_rates(self, ts_detail):
if not ts_detail.billable:
ts_detail.billing_rate = 0.0
- ts_detail.costing_rate = 0.0
@frappe.whitelist()
def get_projectwise_timesheet_data(project, parent=None):
diff --git a/erpnext/public/js/pos/pos_selected_item.html b/erpnext/public/js/pos/pos_selected_item.html
index 6f2772b..085e048 100644
--- a/erpnext/public/js/pos/pos_selected_item.html
+++ b/erpnext/public/js/pos/pos_selected_item.html
@@ -8,7 +8,7 @@
<input type="tel" class="form-control cell" disabled value="{%= price_list_rate %}"/>
</div>
<div class="pos-list-row">
- <div class="cell">{{ __("Discount") }}:</div>
+ <div class="cell">{{ __("Discount") }}: %</div>
<input type="tel" class="form-control cell pos-item-disc" value="{%= discount_percentage %}">
</div>
<div class="pos-list-row">
diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.py b/erpnext/schools/doctype/student_applicant/student_applicant.py
index 465b4e4..d0db658 100644
--- a/erpnext/schools/doctype/student_applicant/student_applicant.py
+++ b/erpnext/schools/doctype/student_applicant/student_applicant.py
@@ -13,9 +13,12 @@
from frappe.model.naming import set_name_by_naming_series
if self.student_admission:
if self.program:
+ # set the naming series from the student admission if provided.
student_admission = get_student_admission_data(self.student_admission, self.program)
if student_admission:
naming_series = student_admission.get("applicant_naming_series")
+ else:
+ naming_series = None
else:
frappe.throw(_("Select the program first"))
@@ -40,15 +43,16 @@
def validation_from_student_admission(self):
student_admission = get_student_admission_data(self.student_admission, self.program)
- if student_admission:
- if ((
- student_admission.minimum_age
- and getdate(student_admission.minimum_age) > getdate(self.date_of_birth)
- ) or (
- student_admission.maximum_age
- and getdate(student_admission.maximum_age) < getdate(self.date_of_birth)
- )):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+ # different validation for minimum and maximum age so that either min/max can also work independently.
+ if student_admission and student_admission.minimum_age and \
+ getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
+ frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+ if student_admission and student_admission.maximum_age and \
+ getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
+ frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
def on_payment_authorized(self, *args, **kwargs):
self.db_set('paid', 1)
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json
index 5a1aeb4..b63fb4b 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.json
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
@@ -12,6 +13,7 @@
"editable_grid": 0,
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -40,6 +42,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -72,6 +75,37 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Description",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -101,6 +135,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -132,6 +167,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -160,6 +196,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -190,19 +227,19 @@
"unique": 0
}
],
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-sitemap",
"idx": 1,
"image_view": 0,
"in_create": 0,
- "in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-02-22 05:06:30.143089",
- "modified_by": "Administrator",
+ "modified": "2017-10-18 14:23:06.538568",
+ "modified_by": "tundebabzy@gmail.com",
"module": "Selling",
"name": "Product Bundle",
"owner": "Administrator",
diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py
index 0f1ee81..7472c51 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.py
+++ b/erpnext/setup/doctype/customer_group/customer_group.py
@@ -17,11 +17,11 @@
def validate_name_with_customer(self):
if frappe.db.exists("Customer", self.name):
- frappe.msgprint(_("An Customer exists with same name"), raise_exception=1)
+ frappe.msgprint(_("A customer with the same name already exists"), raise_exception=1)
def get_parent_customer_groups(customer_group):
lft, rgt = frappe.db.get_value("Customer Group", customer_group, ['lft', 'rgt'])
return frappe.db.sql("""select name from `tabCustomer Group`
where lft <= %s and rgt >= %s
- order by lft asc""", (lft, rgt), as_dict=True)
\ No newline at end of file
+ order by lft asc""", (lft, rgt), as_dict=True)
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 306736f..77c59f4 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -4,7 +4,7 @@
},
"refresh": function(frm) {
- if(frm.doc.status==="Open") {
+ if(frm.doc.status!=="Closed") {
frm.add_custom_button(__("Close"), function() {
frm.set_value("status", "Closed");
frm.save();
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index 6ebe411..7ee3960 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -28,11 +28,12 @@
)).insert(ignore_permissions=True)
opportunity = frappe.get_doc(dict(
- doctype='Opportunity',
+ doctype ='Opportunity',
enquiry_from = 'Customer' if customer else 'Lead',
status = 'Open',
title = subject,
- to_discuss=message
+ contact_email = sender,
+ to_discuss = message
))
if customer:
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index 38c138d..24858f3 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -72,7 +72,6 @@
erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
erpnext/hr/doctype/expense_claim/test_expense_claim.js
erpnext/hr/doctype/training_event/tests/test_training_event.js
-erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
erpnext/hr/doctype/training_result_employee/test_training_result.js
erpnext/hr/doctype/training_feedback/test_training_feedback.js
erpnext/hr/doctype/loan_type/test_loan_type.js
@@ -134,3 +133,4 @@
erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.js
erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.js
erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
+erpnext/projects/doctype/task/tests/test_task_tree.js