From 3900db2333fc073bbb29316a2435284c15c1327f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 10 Jul 2023 20:51:28 +0530 Subject: [PATCH 01/92] feat: set up form, list routing for leaves --- frontend/src/{router.js => router/index.js} | 8 +++--- frontend/src/router/leaves.js | 25 +++++++++++++++++++ .../{Leaves.vue => leaves/Dashboard.vue} | 11 +++++--- frontend/src/views/leaves/Details.vue | 0 frontend/src/views/leaves/Form.vue | 24 ++++++++++++++++++ frontend/src/views/leaves/List.vue | 0 6 files changed, 60 insertions(+), 8 deletions(-) rename frontend/src/{router.js => router/index.js} (91%) create mode 100644 frontend/src/router/leaves.js rename frontend/src/views/{Leaves.vue => leaves/Dashboard.vue} (81%) create mode 100644 frontend/src/views/leaves/Details.vue create mode 100644 frontend/src/views/leaves/Form.vue create mode 100644 frontend/src/views/leaves/List.vue diff --git a/frontend/src/router.js b/frontend/src/router/index.js similarity index 91% rename from frontend/src/router.js rename to frontend/src/router/index.js index a37b32988e..ce8019f51b 100644 --- a/frontend/src/router.js +++ b/frontend/src/router/index.js @@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from "@ionic/vue-router" import { session } from "@/data/session" import { userResource } from "@/data/user" +import leaveRoutes from "./leaves" + const routes = [ { path: "/", @@ -18,16 +20,12 @@ const routes = [ name: "Status", component: () => import("@/views/CheckStatus.vue"), }, - { - path: "/leaves", - name: "Leaves", - component: () => import("@/views/Leaves.vue"), - }, { path: "/profile", name: "Profile", component: () => import("@/views/Profile.vue"), }, + ...leaveRoutes, ] const router = createRouter({ diff --git a/frontend/src/router/leaves.js b/frontend/src/router/leaves.js new file mode 100644 index 0000000000..cf72459db4 --- /dev/null +++ b/frontend/src/router/leaves.js @@ -0,0 +1,25 @@ +const routes = [ + { + name: "Leaves", + path: "/leaves", + component: () => import("@/views/leaves/Dashboard.vue"), + }, + { + name: "LeaveApplicationListView", + path: "/leaves/list", + component: () => import("@/views/leaves/List.vue"), + }, + { + name: "LeaveApplicationFormView", + path: "/leaves/new", + component: () => import("@/views/leaves/Form.vue"), + }, + { + name: "LeaveApplicationDetailView", + path: "/leaves/:id", + props: true, + component: () => import("@/views/leaves/Details.vue"), + } +] + +export default routes \ No newline at end of file diff --git a/frontend/src/views/Leaves.vue b/frontend/src/views/leaves/Dashboard.vue similarity index 81% rename from frontend/src/views/Leaves.vue rename to frontend/src/views/leaves/Dashboard.vue index fab68d6a77..94f8130b84 100644 --- a/frontend/src/views/Leaves.vue +++ b/frontend/src/views/leaves/Dashboard.vue @@ -5,9 +5,14 @@
- + + +
Leaves This Month
diff --git a/frontend/src/views/leaves/Details.vue b/frontend/src/views/leaves/Details.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue new file mode 100644 index 0000000000..4fc3d03e57 --- /dev/null +++ b/frontend/src/views/leaves/Form.vue @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/leaves/List.vue b/frontend/src/views/leaves/List.vue new file mode 100644 index 0000000000..e69de29bb2 From 125a1f2733402506c75a9eb1c82210de6ef9ba2b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 13 Jul 2023 00:59:14 +0530 Subject: [PATCH 02/92] feat: Basic Formview and Fields Rendering --- frontend/src/components/FormField.vue | 184 ++++++++++++++++++++++++++ frontend/src/components/FormView.vue | 59 +++++++++ frontend/src/main.js | 9 +- frontend/src/router/leaves.js | 4 +- frontend/src/views/leaves/Details.vue | 1 + frontend/src/views/leaves/Form.vue | 20 +-- frontend/src/views/leaves/List.vue | 1 + hrms/api/__init__.py | 21 +++ 8 files changed, 281 insertions(+), 18 deletions(-) create mode 100644 frontend/src/components/FormField.vue create mode 100644 frontend/src/components/FormView.vue diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue new file mode 100644 index 0000000000..02719bb6da --- /dev/null +++ b/frontend/src/components/FormField.vue @@ -0,0 +1,184 @@ + + + diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue new file mode 100644 index 0000000000..88e3ab1250 --- /dev/null +++ b/frontend/src/components/FormView.vue @@ -0,0 +1,59 @@ + + + diff --git a/frontend/src/main.js b/frontend/src/main.js index feda9248c6..12ead867b7 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -3,7 +3,13 @@ import App from "./App.vue" import router from "./router" import socket from "./socket" -import { Button, setConfig, frappeRequest, resourcesPlugin } from "frappe-ui" +import { + Button, + Input, + setConfig, + frappeRequest, + resourcesPlugin, +} from "frappe-ui" import { IonicVue } from "@ionic/vue" @@ -26,6 +32,7 @@ setConfig("resourceFetcher", frappeRequest) app.use(resourcesPlugin) app.component("Button", Button) +app.component("Input", Input) app.use(router) app.use(IonicVue) diff --git a/frontend/src/router/leaves.js b/frontend/src/router/leaves.js index cf72459db4..0479b43f23 100644 --- a/frontend/src/router/leaves.js +++ b/frontend/src/router/leaves.js @@ -19,7 +19,7 @@ const routes = [ path: "/leaves/:id", props: true, component: () => import("@/views/leaves/Details.vue"), - } + }, ] -export default routes \ No newline at end of file +export default routes diff --git a/frontend/src/views/leaves/Details.vue b/frontend/src/views/leaves/Details.vue index e69de29bb2..7773617154 100644 --- a/frontend/src/views/leaves/Details.vue +++ b/frontend/src/views/leaves/Details.vue @@ -0,0 +1 @@ + diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue index 4fc3d03e57..bbdd339e26 100644 --- a/frontend/src/views/leaves/Form.vue +++ b/frontend/src/views/leaves/Form.vue @@ -1,24 +1,14 @@ \ No newline at end of file +import FormView from "@/components/FormView.vue" + diff --git a/frontend/src/views/leaves/List.vue b/frontend/src/views/leaves/List.vue index e69de29bb2..3afa42cfe0 100644 --- a/frontend/src/views/leaves/List.vue +++ b/frontend/src/views/leaves/List.vue @@ -0,0 +1 @@ + diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index f8d458f814..eca7d5962a 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -115,3 +115,24 @@ def get_holidays_for_employee(employee: str) -> list[dict]: .where((Holiday.parent == holiday_list) & (Holiday.weekly_off == 0)) .orderby(Holiday.holiday_date, order=Order.asc) ).run(as_dict=True) + + +@frappe.whitelist() +def get_doctype_fields(doctype : str) -> list[dict]: + return frappe.get_meta(doctype).fields + + +@frappe.whitelist() +def get_link_field_options(doctype: str) -> list: + fields = ["name as value"] + title_field = frappe.db.get_value("DocType", doctype, "title_field", cache=1) + + show_title_field_in_link = ( + frappe.db.get_value("DocType", doctype, "show_title_field_in_link", cache=1) == 1 + ) + + if title_field and show_title_field_in_link: + fields.append(f"{title_field} as label") + + link_options = frappe.get_all(doctype, fields=fields) + return link_options \ No newline at end of file From b1aab2bce10a42a1807618d120df4adbb9c908cf Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 14 Jul 2023 13:58:27 +0530 Subject: [PATCH 03/92] refactor(UX): show title with docname in link fields --- frontend/src/components/FormField.vue | 6 +++--- hrms/api/__init__.py | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 02719bb6da..128abf83e5 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -70,8 +70,8 @@ /> -
-

+
+

{{ props.label }}

@@ -154,7 +154,7 @@ function setLinkFieldList() { pageLength: 100, transform: (data) => { return data.map((doc) => ({ - label: doc.label || doc.value, + label: doc.label ? `${doc.label}: ${doc.value}` : doc.value, value: doc.value, })) }, diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index eca7d5962a..01ba5ae04c 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -118,7 +118,7 @@ def get_holidays_for_employee(employee: str) -> list[dict]: @frappe.whitelist() -def get_doctype_fields(doctype : str) -> list[dict]: +def get_doctype_fields(doctype: str) -> list[dict]: return frappe.get_meta(doctype).fields @@ -127,12 +127,8 @@ def get_link_field_options(doctype: str) -> list: fields = ["name as value"] title_field = frappe.db.get_value("DocType", doctype, "title_field", cache=1) - show_title_field_in_link = ( - frappe.db.get_value("DocType", doctype, "show_title_field_in_link", cache=1) == 1 - ) - - if title_field and show_title_field_in_link: + if title_field: fields.append(f"{title_field} as label") link_options = frappe.get_all(doctype, fields=fields) - return link_options \ No newline at end of file + return link_options From 90d5e51af991598c3dc7f14592a42748654f148a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 14 Jul 2023 22:18:08 +0530 Subject: [PATCH 04/92] fix: bind attributes for fieldtypes --- frontend/src/components/FormField.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 128abf83e5..0ccbf554bb 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -24,6 +24,7 @@ :placeholder="`Select ${props.options}`" :options="selectionList" @change="(v) => emit('update:modelValue', v.value)" + v-bind="$attrs" :disabled="props.readOnly" /> @@ -35,6 +36,7 @@ :placeholder="`Enter ${props.label}`" @input="(v) => emit('update:modelValue', v.value)" @change="(v) => emit('change', v.value)" + v-bind="$attrs" :disabled="props.readOnly" /> @@ -46,6 +48,7 @@ :value="modelValue" @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" + v-bind="$attrs" :disabled="props.readOnly" /> @@ -56,6 +59,7 @@ :value="modelValue" @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" + v-bind="$attrs" :disabled="props.readOnly" /> @@ -66,6 +70,7 @@ :value="modelValue" @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" + v-bind="$attrs" :disabled="props.readOnly" /> @@ -86,6 +91,7 @@ :placeholder="`Select ${props.label}`" :formatValue="(val) => dayjs(val).format('DD-MM-YYYY')" @change="(v) => emit('update:modelValue', v.value)" + v-bind="$attrs" :disabled="props.readOnly" /> From 48d21e3e415c62ac509f47c6945302885d8baea2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 17 Jul 2023 13:39:48 +0530 Subject: [PATCH 05/92] feat: set up watchers for form fields to add scripts --- frontend/src/components/FormField.vue | 32 +++++++++++---- frontend/src/components/FormView.vue | 34 +++++++++++----- frontend/src/views/leaves/Form.vue | 57 ++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 0ccbf554bb..3234fb44d1 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue index bbdd339e26..7020464e41 100644 --- a/frontend/src/views/leaves/Form.vue +++ b/frontend/src/views/leaves/Form.vue @@ -1,14 +1,67 @@ From d969712e5165e0a354282448a47e444f7fda82ee Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 17 Jul 2023 16:54:37 +0530 Subject: [PATCH 06/92] refactor: filter form fields to reduce noise --- frontend/src/views/leaves/Form.vue | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue index 7020464e41..ae839e5d96 100644 --- a/frontend/src/views/leaves/Form.vue +++ b/frontend/src/views/leaves/Form.vue @@ -25,7 +25,8 @@ const formFields = createResource({ doctype: "Leave Application", }, transform(data) { - return data.map((field) => { + let fields = getFilteredFields(data) + return fields.map((field) => { if (field.fieldname === "half_day_date") { field.hidden = true } @@ -35,6 +36,22 @@ const formFields = createResource({ }) formFields.reload() +function getFilteredFields(fields) { + // reduce noise from the form view by excluding unnecessary fields + // ex: employee and other details can be fetched from the session user + const excludeFields = [ + "naming_series", + "employee", + "employee_name", + "department", + "company", + "sb_other_details", + "salary_slip", + "letter_head" + ] + return fields.filter((field) => !excludeFields.includes(field.fieldname)) +} + // form scripts watch( () => leaveApplication.half_day, From 9637b514f55af84c18e6aeb3f77b5a183c3b635b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 17 Jul 2023 17:41:28 +0530 Subject: [PATCH 07/92] feat: show error messages for field validations --- frontend/src/components/FormField.vue | 5 ++++- frontend/src/components/FormView.vue | 1 + frontend/src/views/leaves/Form.vue | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 3234fb44d1..a4bb92b1d2 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -98,11 +98,13 @@ + +

From 61513a72ab854a7ee6f873ec8f3ff65e97a9aee7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 17 Jul 2023 22:59:02 +0530 Subject: [PATCH 08/92] feat: Leave Application form scripts - set, validate values --- frontend/src/components/FormField.vue | 4 + frontend/src/components/FormView.vue | 24 +++- frontend/src/views/leaves/Form.vue | 183 +++++++++++++++++++++----- 3 files changed, 171 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index a4bb92b1d2..9c609e431d 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -94,6 +94,8 @@ @change="(v) => emit('change', v)" v-bind="$attrs" :disabled="props.readOnly" + :min="props.minDate" + :max="props.maxDate" /> @@ -121,6 +123,8 @@ const props = defineProps({ default: true, }, errorMessage: String, + minDate: String, + maxDate: String, }) const emit = defineEmits(["change", "update:modelValue"]) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index e13bbc96c5..0287999b05 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -18,7 +18,7 @@ :key="field.name" :fieldtype="field.fieldtype" :fieldname="field.fieldname" - v-model="modelValue[field.fieldname]" + v-model="formModel[field.fieldname]" :default="field.default" :label="field.label" :options="field.options" @@ -26,8 +26,20 @@ :reqd="Boolean(field.reqd)" :hidden="Boolean(field.hidden)" :errorMessage="field.error_message" + :minDate="field.minDate" + :maxDate="field.maxDate" />
+ +
+ +
@@ -56,8 +68,10 @@ const props = defineProps({ required: false, }, }) +const router = useRouter() +const emit = defineEmits(["validateForm"]) -const modelValue = computed({ +const formModel = computed({ get: () => props.modelValue, set: (value) => { emit('update:modelValue', value) @@ -66,9 +80,11 @@ const modelValue = computed({ props.fields?.forEach((field) => { if (field.default) { - modelValue.value[field.fieldname] = field.default + formModel.value[field.fieldname] = field.default } }) -const router = useRouter() +function createNewDocument() { + emit("validateForm") +} diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue index 835a35d13a..13477233a9 100644 --- a/frontend/src/views/leaves/Form.vue +++ b/frontend/src/views/leaves/Form.vue @@ -1,41 +1,85 @@ From bf791e2f780100a8ff80c36e47306d626f90a3fd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 17 Jul 2023 23:36:34 +0530 Subject: [PATCH 09/92] refactor: get all leave approval details in a single request --- frontend/src/views/leaves/Form.vue | 24 +++++++++--------------- hrms/api/__init__.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leaves/Form.vue index 13477233a9..13b47fd352 100644 --- a/frontend/src/views/leaves/Form.vue +++ b/frontend/src/views/leaves/Form.vue @@ -38,7 +38,7 @@ const formFields = createResource({ field.default = dayjs().format("YYYY-MM-DD") if (field.fieldname === "leave_approver") - field.reqd = isApproverMandatory.data + field.reqd = leaveApprovalDetails?.data?.is_mandatory return field }) @@ -46,12 +46,11 @@ const formFields = createResource({ }) formFields.reload() -const isApproverMandatory = createResource({ - url: "hrms.hr.doctype.leave_application.leave_application.get_mandatory_approval", - params: { doctype: "Leave Application" }, +const leaveApprovalDetails = createResource({ + url: "hrms.api.get_leave_approval_details", + params: { employee: employee.data.name }, }) -isApproverMandatory.reload() - +leaveApprovalDetails.reload() // form scripts watch( @@ -174,14 +173,8 @@ function setHalfDayDateRange() { } function setLeaveApprover() { - const leaveApprover = createResource({ - url: "hrms.hr.doctype.leave_application.leave_application.get_leave_approver", - params: { employee: employee.data.name }, - onSuccess(data) { - leaveApplication.leave_approver = data - } - }) - leaveApprover.reload() + leaveApplication.leave_approver = leaveApprovalDetails.data.leave_approver + leaveApplication.leave_approver_name = leaveApprovalDetails.data.leave_approver_name } function areValuesSet() { @@ -192,7 +185,8 @@ function areValuesSet() { ) } -onMounted(() => { +onMounted(async () => { + await leaveApprovalDetails.promise setLeaveApprover() setTotalLeaveDays() }) diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 01ba5ae04c..3e6750adb7 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -117,6 +117,21 @@ def get_holidays_for_employee(employee: str) -> list[dict]: ).run(as_dict=True) +@frappe.whitelist() +def get_leave_approval_details(employee: str) -> dict: + from hrms.hr.doctype.leave_application.leave_application import get_leave_approver + + leave_approver = get_leave_approver(employee) + + return dict( + leave_approver=leave_approver, + leave_approver_name=frappe.db.get_value("User", leave_approver, "full_name", cache=True), + is_mandatory=frappe.db.get_single_value( + "HR Settings", "leave_approver_mandatory_in_leave_application" + ), + ) + + @frappe.whitelist() def get_doctype_fields(doctype: str) -> list[dict]: return frappe.get_meta(doctype).fields From 1b8b7c9046f1ad6b00688d3dba84264c161112fb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 23 Jul 2023 00:02:46 +0530 Subject: [PATCH 10/92] fix: handle empty states in views --- frontend/src/components/EmptyState.vue | 11 +++++++++++ frontend/src/components/Holidays.vue | 5 ++++- frontend/src/components/LeaveBalance.vue | 6 ++++-- frontend/src/components/RequestList.vue | 6 ++---- frontend/src/main.js | 8 +++++--- hrms/api/__init__.py | 2 +- 6 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/EmptyState.vue diff --git a/frontend/src/components/EmptyState.vue b/frontend/src/components/EmptyState.vue new file mode 100644 index 0000000000..8a091b46ee --- /dev/null +++ b/frontend/src/components/EmptyState.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Holidays.vue b/frontend/src/components/Holidays.vue index 6ecbdd1f61..d8bf2c179f 100644 --- a/frontend/src/components/Holidays.vue +++ b/frontend/src/components/Holidays.vue @@ -3,6 +3,7 @@
Upcoming Holidays
@@ -10,7 +11,7 @@
-
+
+ +
Leave Balance
-
+
View Leave History
-
+
+ +
diff --git a/frontend/src/components/RequestList.vue b/frontend/src/components/RequestList.vue index b4b56e4486..d60fcb83e9 100644 --- a/frontend/src/components/RequestList.vue +++ b/frontend/src/components/RequestList.vue @@ -1,7 +1,7 @@ @@ -62,8 +144,9 @@ From 4745421872bc8f9d0e5a92caa2c5ded656a404ed Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 31 Jul 2023 00:25:10 +0530 Subject: [PATCH 22/92] feat: field level filters with conditions - extract `ListFilterActionSheet` component - implement clear filters - fix filter button style --- frontend/package.json | 4 +- frontend/src/components/FormField.vue | 2 +- .../src/components/ListFiltersActionSheet.vue | 129 ++++++++++++++++++ frontend/src/components/ListView.vue | 119 +++++++--------- frontend/src/views/leaves/List.vue | 34 ++++- frontend/yarn.lock | 42 +++--- 6 files changed, 236 insertions(+), 94 deletions(-) create mode 100644 frontend/src/components/ListFiltersActionSheet.vue diff --git a/frontend/package.json b/frontend/package.json index d8a8663c31..fde4f37db6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,8 +11,8 @@ "copy-html-entry": "cp ../hrms/public/frontend/index.html ../hrms/www/hrms.html" }, "dependencies": { - "@ionic/vue": "^7.0.4", - "@ionic/vue-router": "^7.0.4", + "@ionic/vue": "^7.2.1", + "@ionic/vue-router": "^7.2.1", "dayjs": "^1.11.7", "feather-icons": "^4.28.0", "frappe-ui": "^0.0.112", diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 3ae0b9c341..d7e103631d 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -118,7 +118,7 @@ const props = defineProps({ reqd: Boolean, hidden: { type: Boolean, - default: true, + default: false, }, errorMessage: String, minDate: String, diff --git a/frontend/src/components/ListFiltersActionSheet.vue b/frontend/src/components/ListFiltersActionSheet.vue new file mode 100644 index 0000000000..c22039122c --- /dev/null +++ b/frontend/src/components/ListFiltersActionSheet.vue @@ -0,0 +1,129 @@ + + + diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue index 36f5f44ce6..e6950e3f87 100644 --- a/frontend/src/components/ListView.vue +++ b/frontend/src/components/ListView.vue @@ -17,12 +17,12 @@
- -
-
- Filters -
-
- -
-
Status
-
- -
-
- - -
- - -
-
-
+ +
@@ -143,13 +100,14 @@ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 890caa85cb..c9f4810b51 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -992,28 +992,28 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@ionic/core@7.0.7": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@ionic/core/-/core-7.0.7.tgz#d09f0daed59d03b0289284598abba24b9280fe96" - integrity sha512-3ps2j3Utx49CVazKHHWsPS8QBZYujnh6/dwz1ovHIDTq7HXKzUNbMtHm0UAZC2YkW5rE/Pm41CIBMwfFV0ImKg== +"@ionic/core@7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@ionic/core/-/core-7.2.1.tgz#8ccc3257446f36d9031a083e655fcd65eebae6cf" + integrity sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A== dependencies: - "@stencil/core" "^3.2.2" - ionicons "^7.1.0" + "@stencil/core" "^3.4.0" + ionicons "7.1.0" tslib "^2.1.0" -"@ionic/vue-router@^7.0.4": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@ionic/vue-router/-/vue-router-7.0.7.tgz#7d018ecc9bed0b256f37ab3634969dd575d908de" - integrity sha512-zpWfCXwcHNipqe1zLDDyN+Is6m9tT5BhFSO4Omw7/z2puYSE0+bLZ/6FeuiBaOBA7Iy85C0IS3r84Nr8Sle7pA== +"@ionic/vue-router@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@ionic/vue-router/-/vue-router-7.2.1.tgz#a7c3f94c5c26df2b93c372d47cd1d5eb193c5b05" + integrity sha512-eUFqsrwT64fAlur+RqkC3rV5srBrjrx3qVG3LeRIE8UM0mEK/SEetEIjlzQx+/PH94RpkAWsmsT8YbddTWKQcg== dependencies: - "@ionic/vue" "7.0.7" + "@ionic/vue" "7.2.1" -"@ionic/vue@7.0.7", "@ionic/vue@^7.0.4": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@ionic/vue/-/vue-7.0.7.tgz#7365c7e7c89cd05f0aee30288d95bcaaaaef5ff7" - integrity sha512-RpsAcfbdW4+0ovLNk+D7O4TgtvMGegk0KXXdNFomudpSzZMfxHxWDj9MuBjpvPH6yNW+0VjsAPviindV/iMdew== +"@ionic/vue@7.2.1", "@ionic/vue@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@ionic/vue/-/vue-7.2.1.tgz#11c427404f1bb66d663d09b5241f37ec168af4ad" + integrity sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw== dependencies: - "@ionic/core" "7.0.7" + "@ionic/core" "7.2.1" ionicons "^7.0.0" "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": @@ -1209,10 +1209,10 @@ resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.22.3.tgz#83987e20bba855c450f6d6780e3a20192603f13f" integrity sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng== -"@stencil/core@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@stencil/core/-/core-3.2.2.tgz#cf3b4ffb7992d0197aa1987dbc5236c4eb98cb6b" - integrity sha512-wXb9cVWL0T3cTwYLveekdTFCRGx6+9hpVDEXna+N8K8OPoW6xtFAHRLv+LjOM7k59PkA8MG3IinAfV7Y+xa0Hw== +"@stencil/core@^3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@stencil/core/-/core-3.4.2.tgz#57ce7f71fe18c2ec0967821bec667fc453cca962" + integrity sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ== "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" @@ -2751,7 +2751,7 @@ internal-slot@^1.0.3, internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" -ionicons@^7.0.0, ionicons@^7.1.0: +ionicons@7.1.0, ionicons@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-7.1.0.tgz#25daa91345acedcb0f4fb7da670f5aff2e1f266a" integrity sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg== From 97c773d42ab0b2aee38798107399df9b99b7962e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 2 Aug 2023 11:01:48 +0530 Subject: [PATCH 23/92] feat: add expense claim routes and refactor leave routing --- frontend/src/components/MenuLinks.vue | 10 ++++---- frontend/src/router/claims.js | 25 +++++++++++++++++++ frontend/src/router/index.js | 2 ++ frontend/src/router/leaves.js | 16 ++++++------ .../src/views/expense_claim/Dashboard.vue | 11 ++++++++ frontend/src/views/expense_claim/Form.vue | 11 ++++++++ frontend/src/views/expense_claim/List.vue | 11 ++++++++ .../src/views/{leaves => leave}/Dashboard.vue | 0 frontend/src/views/{leaves => leave}/Form.vue | 0 frontend/src/views/{leaves => leave}/List.vue | 0 10 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 frontend/src/router/claims.js create mode 100644 frontend/src/views/expense_claim/Dashboard.vue create mode 100644 frontend/src/views/expense_claim/Form.vue create mode 100644 frontend/src/views/expense_claim/List.vue rename frontend/src/views/{leaves => leave}/Dashboard.vue (100%) rename frontend/src/views/{leaves => leave}/Form.vue (100%) rename frontend/src/views/{leaves => leave}/List.vue (100%) diff --git a/frontend/src/components/MenuLinks.vue b/frontend/src/components/MenuLinks.vue index 12cbc31a47..c323ce7cf9 100644 --- a/frontend/src/components/MenuLinks.vue +++ b/frontend/src/components/MenuLinks.vue @@ -49,16 +49,16 @@ const menuItems = ref([ current: false, }, { - icon: "check-circle", - title: "Attendance", + icon: "dollar-sign", + title: "Expense Claims", route: { - name: "Login", + name: "ExpenseClaims", }, current: false, }, { - icon: "dollar-sign", - title: "Expense Claims", + icon: "check-circle", + title: "Attendance", route: { name: "Login", }, diff --git a/frontend/src/router/claims.js b/frontend/src/router/claims.js new file mode 100644 index 0000000000..fce7fb5cfc --- /dev/null +++ b/frontend/src/router/claims.js @@ -0,0 +1,25 @@ +const routes = [ + { + name: "ExpenseClaims", + path: "/expense-claim/dashboard", + component: () => import("@/views/expense_claim/Dashboard.vue"), + }, + { + name: "ExpenseClaimListView", + path: "/expense-claim/list", + component: () => import("@/views/expense_claim/List.vue"), + }, + { + name: "ExpenseClaimFormView", + path: "/expense-claim/new", + component: () => import("@/views/expense_claim/Form.vue"), + }, + { + name: "ExpenseClaimDetailView", + path: "/expense-claim/:id", + props: true, + component: () => import("@/views/expense_claim/Form.vue"), + }, +] + +export default routes diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index ce8019f51b..6aa7334860 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -3,6 +3,7 @@ import { session } from "@/data/session" import { userResource } from "@/data/user" import leaveRoutes from "./leaves" +import claimRoutes from "./claims" const routes = [ { @@ -26,6 +27,7 @@ const routes = [ component: () => import("@/views/Profile.vue"), }, ...leaveRoutes, + ...claimRoutes, ] const router = createRouter({ diff --git a/frontend/src/router/leaves.js b/frontend/src/router/leaves.js index af342f4750..16620ab60f 100644 --- a/frontend/src/router/leaves.js +++ b/frontend/src/router/leaves.js @@ -1,24 +1,24 @@ const routes = [ { name: "Leaves", - path: "/leaves", - component: () => import("@/views/leaves/Dashboard.vue"), + path: "/leave/dashboard", + component: () => import("@/views/leave/Dashboard.vue"), }, { name: "LeaveApplicationListView", - path: "/leaves/list", - component: () => import("@/views/leaves/List.vue"), + path: "/leave/list", + component: () => import("@/views/leave/List.vue"), }, { name: "LeaveApplicationFormView", - path: "/leaves/new", - component: () => import("@/views/leaves/Form.vue"), + path: "/leave/new", + component: () => import("@/views/leave/Form.vue"), }, { name: "LeaveApplicationDetailView", - path: "/leaves/:id", + path: "/leave/:id", props: true, - component: () => import("@/views/leaves/Form.vue"), + component: () => import("@/views/leave/Form.vue"), }, ] diff --git a/frontend/src/views/expense_claim/Dashboard.vue b/frontend/src/views/expense_claim/Dashboard.vue new file mode 100644 index 0000000000..3e82cf1843 --- /dev/null +++ b/frontend/src/views/expense_claim/Dashboard.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue new file mode 100644 index 0000000000..0b49103e4c --- /dev/null +++ b/frontend/src/views/expense_claim/Form.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/views/expense_claim/List.vue b/frontend/src/views/expense_claim/List.vue new file mode 100644 index 0000000000..f57d33681f --- /dev/null +++ b/frontend/src/views/expense_claim/List.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/views/leaves/Dashboard.vue b/frontend/src/views/leave/Dashboard.vue similarity index 100% rename from frontend/src/views/leaves/Dashboard.vue rename to frontend/src/views/leave/Dashboard.vue diff --git a/frontend/src/views/leaves/Form.vue b/frontend/src/views/leave/Form.vue similarity index 100% rename from frontend/src/views/leaves/Form.vue rename to frontend/src/views/leave/Form.vue diff --git a/frontend/src/views/leaves/List.vue b/frontend/src/views/leave/List.vue similarity index 100% rename from frontend/src/views/leaves/List.vue rename to frontend/src/views/leave/List.vue From 625504c5b3c91a930d38145643e31a52f38d94ed Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 2 Aug 2023 16:24:24 +0530 Subject: [PATCH 24/92] feat: Expense Claim summary in dashboard --- .../src/components/ExpenseClaimSummary.vue | 81 +++++++++++++++++++ frontend/src/components/LeaveBalance.vue | 2 +- .../src/views/expense_claim/Dashboard.vue | 16 +++- hrms/api/__init__.py | 48 +++++++++++ 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/ExpenseClaimSummary.vue diff --git a/frontend/src/components/ExpenseClaimSummary.vue b/frontend/src/components/ExpenseClaimSummary.vue new file mode 100644 index 0000000000..ff19c7765a --- /dev/null +++ b/frontend/src/components/ExpenseClaimSummary.vue @@ -0,0 +1,81 @@ + + + diff --git a/frontend/src/components/LeaveBalance.vue b/frontend/src/components/LeaveBalance.vue index 81b2d268e3..611539a70a 100644 --- a/frontend/src/components/LeaveBalance.vue +++ b/frontend/src/components/LeaveBalance.vue @@ -50,7 +50,7 @@ diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 48af7d6b3c..ca454668e9 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -23,6 +23,7 @@ def get_current_employee_info() -> dict: return employee +# Leaves and Holidays @frappe.whitelist() def get_leave_applications(filters: dict) -> list[dict]: doctype = "Leave Application" @@ -188,6 +189,53 @@ def get_leave_types(employee: str, date: str) -> list: return leave_types +# Expense Claims +@frappe.whitelist() +def get_expense_claim_summary(employee: str) -> dict: + from frappe.query_builder.functions import Sum + + Claim = frappe.qb.DocType("Expense Claim") + + pending_claims_case = ( + frappe.qb.terms.Case() + .when(Claim.approval_status == "Draft", Claim.total_claimed_amount) + .else_(0) + ) + sum_pending_claims = Sum(pending_claims_case).as_("total_pending_amount") + + approved_claims_case = ( + frappe.qb.terms.Case() + .when(Claim.approval_status == "Approved", Claim.total_sanctioned_amount) + .else_(0) + ) + sum_approved_claims = Sum(approved_claims_case).as_("total_approved_amount") + + rejected_claims_case = ( + frappe.qb.terms.Case() + .when(Claim.approval_status == "Rejected", Claim.total_sanctioned_amount) + .else_(0) + ) + sum_rejected_claims = Sum(rejected_claims_case).as_("total_rejected_amount") + + summary = ( + frappe.qb.from_(Claim) + .select( + sum_pending_claims, + sum_approved_claims, + sum_rejected_claims, + Claim.company, + ) + .where((Claim.docstatus != 2) & (Claim.employee == employee)) + ).run(as_dict=True)[0] + + currency = frappe.db.get_value("Company", summary.company, "default_currency") + symbol = frappe.db.get_value("Currency", currency, "symbol") + summary["currency"] = symbol or currency + + return summary + + +# Form View APIs @frappe.whitelist() def get_doctype_fields(doctype: str) -> list[dict]: return frappe.get_meta(doctype).fields From f5c31701d038477156a0a2cb457bccc585fe4e65 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 3 Aug 2023 15:36:27 +0530 Subject: [PATCH 25/92] feat: show recent expenses in Claims dashboard - show claim types, amount, dates in list item - add resource for company wise currency symbols as its needed frequently --- frontend/src/components/ExpenseClaimItem.vue | 86 +++++++++++++++++++ .../src/components/ExpenseClaimSummary.vue | 22 ++--- frontend/src/components/RequestList.vue | 27 ++++++ frontend/src/data/claims.js | 35 ++++++++ frontend/src/data/company.js | 10 +++ .../src/views/expense_claim/Dashboard.vue | 14 ++- hrms/api/__init__.py | 72 +++++++++++++++- 7 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/ExpenseClaimItem.vue create mode 100644 frontend/src/data/claims.js create mode 100644 frontend/src/data/company.js diff --git a/frontend/src/components/ExpenseClaimItem.vue b/frontend/src/components/ExpenseClaimItem.vue new file mode 100644 index 0000000000..cd5ed8f8e0 --- /dev/null +++ b/frontend/src/components/ExpenseClaimItem.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/components/ExpenseClaimSummary.vue b/frontend/src/components/ExpenseClaimSummary.vue index ff19c7765a..bb29096bc7 100644 --- a/frontend/src/components/ExpenseClaimSummary.vue +++ b/frontend/src/components/ExpenseClaimSummary.vue @@ -7,7 +7,7 @@ >Total Expense Amount - {{ `${summary.data?.currency} ${total_claimed_amount}` }} + {{ `${company_currency} ${total_claimed_amount}` }} @@ -20,9 +20,7 @@ - {{ - `${summary.data?.currency} ${summary.data?.total_pending_amount}` - }} + {{ `${company_currency} ${summary.data?.total_pending_amount}` }}
@@ -33,17 +31,15 @@
- {{ - `${summary.data?.currency} ${summary.data?.total_approved_amount}` - }} + {{ `${company_currency} ${summary.data?.total_approved_amount}` }}
- Rejected + + Rejected +
@@ -61,6 +57,8 @@ import { FeatherIcon, createResource } from "frappe-ui" import { computed, inject } from "vue" +import { getCompanyCurrency } from "@/data/company" + const employee = inject("$employee") const summary = createResource({ @@ -78,4 +76,8 @@ const total_claimed_amount = computed(() => { summary.data?.total_rejected_amount ) }) + +const company_currency = computed(() => + getCompanyCurrency(employee.data.company) +) diff --git a/frontend/src/components/RequestList.vue b/frontend/src/components/RequestList.vue index d9ede6a137..8cfef415db 100644 --- a/frontend/src/components/RequestList.vue +++ b/frontend/src/components/RequestList.vue @@ -14,7 +14,25 @@ :doc="link" :isTeamRequest="props.teamRequests" /> +
+ + + + @@ -34,6 +52,7 @@ import { ref } from "vue" import { IonModal } from "@ionic/vue" import LeaveRequestItem from "@/components/LeaveRequestItem.vue" +import ExpenseClaimItem from "@/components/ExpenseClaimItem.vue" import RequestActionSheet from "@/components/RequestActionSheet.vue" import { LEAVE_FIELDS } from "@/data/config/requestSummaryFields" @@ -46,6 +65,14 @@ const props = defineProps({ type: Boolean, default: false, }, + addListButton: { + type: Boolean, + default: false, + }, + listButtonRoute: { + type: String, + default: "", + }, }) const isRequestModalOpen = ref(false) diff --git a/frontend/src/data/claims.js b/frontend/src/data/claims.js new file mode 100644 index 0000000000..e7630ccadb --- /dev/null +++ b/frontend/src/data/claims.js @@ -0,0 +1,35 @@ +import { createResource } from "frappe-ui" +import { employeeResource } from "./employee" + +const transformClaimData = (data) => { + return data.map((claim) => { + claim.doctype = "Expense Claim" + return claim + }) +} + +export const myRequests = createResource({ + url: "hrms.api.get_expense_claims", + params: { + employee: employeeResource.data.name, + limit: 5, + }, + auto: true, + transform(data) { + return transformClaimData(data) + }, +}) + +export const teamRequests = createResource({ + url: "hrms.api.get_expense_claims", + params: { + employee: employeeResource.data.name, + approver_id: employeeResource.data.user_id, + for_approval: 1, + limit: 5, + }, + auto: true, + transform(data) { + return transformClaimData(data) + }, +}) diff --git a/frontend/src/data/company.js b/frontend/src/data/company.js new file mode 100644 index 0000000000..53a6593ec9 --- /dev/null +++ b/frontend/src/data/company.js @@ -0,0 +1,10 @@ +import { createResource } from "frappe-ui" + +const currencies = createResource({ + url: "hrms.api.get_company_currencies", + auto: true, +}) + +export function getCompanyCurrency(company) { + return currencies?.data?.[company] +} diff --git a/frontend/src/views/expense_claim/Dashboard.vue b/frontend/src/views/expense_claim/Dashboard.vue index 9adb4b012d..48993d89bd 100644 --- a/frontend/src/views/expense_claim/Dashboard.vue +++ b/frontend/src/views/expense_claim/Dashboard.vue @@ -1,7 +1,7 @@ @@ -32,6 +39,8 @@ import BaseLayout from "@/components/BaseLayout.vue" import ExpenseClaimSummary from "@/components/ExpenseClaimSummary.vue" import RequestList from "@/components/RequestList.vue" +import EmployeeAdvanceBalance from "@/components/EmployeeAdvanceBalance.vue" import { myRequests } from "@/data/claims" +import { advanceBalance } from "@/data/advances" diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 5e8b95c304..bf22b297aa 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -199,7 +199,6 @@ def get_expense_claims( ) -> list[dict]: Claim = frappe.qb.DocType("Expense Claim") ClaimDetail = frappe.qb.DocType("Expense Claim Detail") - Company = frappe.qb.DocType("Company") query = ( frappe.qb.from_(Claim) @@ -285,6 +284,36 @@ def get_expense_claim_summary(employee: str) -> dict: return summary +# Employee Advance +@frappe.whitelist() +def get_employee_advance_balance(employee: str) -> list[dict]: + Advance = frappe.qb.DocType("Employee Advance") + + advances = ( + frappe.qb.from_(Advance) + .select( + Advance.name, + Advance.employee, + Advance.status, + Advance.purpose, + Advance.paid_amount, + (Advance.paid_amount - (Advance.claimed_amount + Advance.return_amount)).as_("balance_amount"), + Advance.posting_date, + Advance.currency, + ) + .where( + (Advance.docstatus == 1) + & (Advance.paid_amount) + & (Advance.employee == employee) + # don't need claimed & returned advances, only partly or completely paid ones + & (Advance.status.isin(["Paid", "Unpaid"])) + ) + .orderby(Advance.posting_date, order=Order.desc) + ).run(as_dict=True) + + return advances + + @frappe.whitelist() def get_company_currencies() -> dict: Company = frappe.qb.DocType("Company") @@ -305,6 +334,15 @@ def get_company_currencies() -> dict: return {company.name: company.symbol or company.default_currency for company in companies} +@frappe.whitelist() +def get_currency_symbols() -> dict: + Currency = frappe.qb.DocType("Currency") + + currencies = (frappe.qb.from_(Currency).select(Currency.name, Currency.symbol)).run(as_dict=True) + + return {currency.name: currency.symbol or currency.name for currency in currencies} + + # Form View APIs @frappe.whitelist() def get_doctype_fields(doctype: str) -> list[dict]: From 4b732f3a0476a50c4b0a13ed7fd83b61e8969c16 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 3 Aug 2023 20:06:17 +0530 Subject: [PATCH 27/92] feat: show claim status along with approval status --- frontend/src/components/ExpenseClaimItem.vue | 15 +++++++++++++-- hrms/api/__init__.py | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ExpenseClaimItem.vue b/frontend/src/components/ExpenseClaimItem.vue index 271a006a23..6105d16d24 100644 --- a/frontend/src/components/ExpenseClaimItem.vue +++ b/frontend/src/components/ExpenseClaimItem.vue @@ -19,7 +19,12 @@
- + + {{ props.doc.status }} + +
@@ -57,7 +62,7 @@ const props = defineProps({ const colorMap = { Approved: "green", Rejected: "red", - Draft: "yellow", + Pending: "yellow", } const claimTitle = computed(() => { @@ -83,4 +88,10 @@ const claimDates = computed(() => { }) const currency = computed(() => getCompanyCurrency(props.doc.company)) + +const approvalStatus = computed(() => { + return props.doc.approval_status === "Draft" + ? "Pending" + : props.doc.approval_status +}) diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index bf22b297aa..866e78adb7 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -209,6 +209,7 @@ def get_expense_claims( Claim.employee, Claim.employee_name, Claim.approval_status, + Claim.status, Claim.expense_approver, Claim.total_claimed_amount, Claim.posting_date, From 7350f94589bcf747e7c694d31f33239cf2d3b42d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 4 Aug 2023 01:45:57 +0530 Subject: [PATCH 28/92] feat: Expense Claim list view --- .../src/components/ListFiltersActionSheet.vue | 10 ++-- frontend/src/components/ListView.vue | 7 +++ frontend/src/views/expense_claim/List.vue | 53 ++++++++++++++++++- hrms/api/__init__.py | 4 +- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/ListFiltersActionSheet.vue b/frontend/src/components/ListFiltersActionSheet.vue index c22039122c..cc1cd4274d 100644 --- a/frontend/src/components/ListFiltersActionSheet.vue +++ b/frontend/src/components/ListFiltersActionSheet.vue @@ -11,14 +11,14 @@ class="flex flex-col w-full gap-1" > -
+
{{ filter.label }}
-
+
+
    +
  • + +
  • +
+
+ + + +
+ +
+ +
+ + +
From ca395e20be247dc6341273b75c116358fe0208cf Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 6 Aug 2023 16:05:14 +0530 Subject: [PATCH 31/92] fix: make save button sticky --- frontend/src/components/FormView.vue | 50 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index a6317d14f5..4e87afa95d 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -1,6 +1,8 @@ diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue index 211a19c887..dbf4100bbf 100644 --- a/frontend/src/components/ListView.vue +++ b/frontend/src/components/ListView.vue @@ -188,7 +188,8 @@ function initializeFilters() { initializeFilters() function prepareFilters() { - let condition, value = "" + let condition = "" + let value = "" appliedFilters.value = [] for (const fieldname in filterMap) { diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index d4c0835254..524d18153e 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -9,19 +9,103 @@ :id="props.id" :tabbedView="true" :tabs="tabs" - /> + > + + + From fb2461418e09ea9520c14a5d325d9285d2d8e3e9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 7 Aug 2023 13:41:59 +0530 Subject: [PATCH 35/92] refactor: cleanup expense taxes and charges table form --- .../doctype/expense_claim/expense_claim.json | 18 +++++++++--------- .../expense_taxes_and_charges.json | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/hrms/hr/doctype/expense_claim/expense_claim.json b/hrms/hr/doctype/expense_claim/expense_claim.json index fb5372303c..80175db293 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.json +++ b/hrms/hr/doctype/expense_claim/expense_claim.json @@ -18,7 +18,7 @@ "approval_status", "expense_details", "expenses", - "sb1", + "taxes_and_charges_sb", "taxes", "advance_payments", "advances", @@ -159,13 +159,6 @@ "options": "Expense Claim Detail", "reqd": 1 }, - { - "collapsible": 1, - "fieldname": "sb1", - "fieldtype": "Section Break", - "label": "Taxes & Charges", - "options": "Simple" - }, { "default": "Today", "fieldname": "posting_date", @@ -378,13 +371,20 @@ "fieldname": "expenses_and_advances_tab", "fieldtype": "Tab Break", "label": "Expenses & Advances" + }, + { + "collapsible": 1, + "fieldname": "taxes_and_charges_sb", + "fieldtype": "Section Break", + "label": "Taxes & Charges", + "options": "Simple" } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-05 18:33:30.144486", + "modified": "2023-08-07 11:04:55.259241", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/hrms/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/hrms/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index 6284f6f966..cfbee2d5d3 100644 --- a/hrms/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/hrms/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -9,12 +9,12 @@ "field_order": [ "account_head", "rate", + "tax_amount", "col_break1", + "total", + "description_sb", "description", - "section_break_6", - "tax_amount", "column_break_8", - "total", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -81,10 +81,6 @@ "options": "currency", "read_only": 1 }, - { - "fieldname": "section_break_6", - "fieldtype": "Section Break" - }, { "fieldname": "column_break_8", "fieldtype": "Column Break" @@ -103,11 +99,15 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "fieldname": "description_sb", + "fieldtype": "Section Break" } ], "istable": 1, "links": [], - "modified": "2023-06-26 13:06:28.555076", + "modified": "2023-08-07 13:39:17.889817", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges", From 0dea28b78d40c2843285c97f56d96a593b591b1e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 7 Aug 2023 13:43:32 +0530 Subject: [PATCH 36/92] fix: fix disabled prop type warnings --- frontend/src/components/FormField.vue | 32 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue index 489ba45000..c4311ff372 100644 --- a/frontend/src/components/FormField.vue +++ b/frontend/src/components/FormField.vue @@ -22,7 +22,7 @@ :options="selectionList" @change="(v) => emit('update:modelValue', v.value)" v-bind="$attrs" - :disabled="props.readOnly" + :disabled="isReadOnly" /> @@ -38,7 +38,7 @@ @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" v-bind="$attrs" - :disabled="props.readOnly" + :disabled="isReadOnly" /> @@ -50,7 +50,7 @@ @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" v-bind="$attrs" - :disabled="props.readOnly" + :disabled="isReadOnly" /> @@ -61,18 +61,18 @@ @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" v-bind="$attrs" - :disabled="props.readOnly" + :disabled="isReadOnly" /> @@ -98,7 +98,7 @@ @input="(v) => emit('update:modelValue', v)" @change="(v) => emit('change', v)" v-bind="$attrs" - :disabled="props.readOnly" + :disabled="isReadOnly" :min="props.minDate" :max="props.maxDate" /> @@ -166,6 +166,14 @@ const showField = computed(() => { return SUPPORTED_FIELD_TYPES.includes(props.fieldtype) && !props.hidden }) +const isNumberType = computed(() => { + return ["Int", "Float", "Currency"].includes(props.fieldtype) +}) + +const isReadOnly = computed(() => { + return Boolean(props.readOnly) +}) + const selectionList = computed(() => { if (props.fieldtype == "Link" && props.options) { return props.documentList || linkFieldList.value.data @@ -206,11 +214,15 @@ function setDefaultValue() { if (props.modelValue) return if (props.default) { - if (props.fieldtype === "Check") + if (props.fieldtype === "Check") { emit("update:modelValue", props.default === "1" ? true : false) - else if (props.fieldtype === "Date" && props.default === "Today") + } else if (props.fieldtype === "Date" && props.default === "Today") { emit("update:modelValue", dayjs().format("YYYY-MM-DD")) - else emit("update:modelValue", props.default) + } else if (isNumberType.value) { + emit("update:modelValue", parseFloat(props.default || 0)) + } else { + emit("update:modelValue", props.default) + } } else { props.fieldtype === "Check" ? emit("update:modelValue", false) From 6a71b42aeaab538ab6e047dbb4486c50ba3554f8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 7 Aug 2023 13:44:04 +0530 Subject: [PATCH 37/92] fix: fields by tab logic --- frontend/src/components/FormView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index 5e84b786ed..0561e4ea03 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -183,7 +183,7 @@ const tabFields = computed(() => { lastFieldIndex = props.fields.findIndex( (field) => field.fieldname === tab.lastField ) - fieldList = props.fields.slice(firstFieldIndex, lastFieldIndex) + fieldList = props.fields.slice(firstFieldIndex, lastFieldIndex + 1) fieldsByTab[tab.name] = fieldList firstFieldIndex = lastFieldIndex + 1 }) From e0eb8d6c672de8013ebdee65243ccf19f8a75a76 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 7 Aug 2023 13:49:12 +0530 Subject: [PATCH 38/92] feat: expense taxes & charges child table --- .../components/AddExpenseTaxActionSheet.vue | 95 ++++++++++++ frontend/src/views/expense_claim/Form.vue | 135 +++++++++++++++++- 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/AddExpenseTaxActionSheet.vue diff --git a/frontend/src/components/AddExpenseTaxActionSheet.vue b/frontend/src/components/AddExpenseTaxActionSheet.vue new file mode 100644 index 0000000000..c39f2a5319 --- /dev/null +++ b/frontend/src/components/AddExpenseTaxActionSheet.vue @@ -0,0 +1,95 @@ + + + diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index 524d18153e..2ab8deae7e 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -17,7 +17,7 @@

Expenses

- {{`${currency} ${expenseClaim.total_claimed_amount || 0}`}} + {{ `${currency} ${expenseClaim.total_claimed_amount || 0}` }}
- @@ -93,6 +103,12 @@ :minDate="field.minDate" :maxDate="field.maxDate" /> + + @@ -132,6 +148,9 @@ import { toast, } from "frappe-ui" import FormField from "@/components/FormField.vue" +import FileUploaderView from "@/components/FileUploaderView.vue" + +import { FileAttachmentUploader } from "@/composables/index" const props = defineProps({ doctype: { @@ -159,10 +178,16 @@ const props = defineProps({ type: Array, required: false, }, + showAttachmentView: { + type: Boolean, + required: false, + default: false, + }, }) const emit = defineEmits(["validateForm", "update:modelValue"]) const router = useRouter() const activeTab = ref(props.tabs?.[0].name) +let fileAttachments = ref([]) const formModel = computed({ get() { @@ -191,11 +216,29 @@ const tabFields = computed(() => { return fieldsByTab }) +const handleFileSelect = (e) => { + fileAttachments.value.push(...e.target.files) +} + +const uploadAttachment = async (doctype, name, file) => { + const fileAttachmentUploader = new FileAttachmentUploader(file) + return fileAttachmentUploader.upload(doctype, name).promise +} + +async function uploadAllAttachments(documentType, documentName) { + for (const attachment of fileAttachments.value) { + if (!attachment.uploaded) { + await uploadAttachment(documentType, documentName, attachment) + attachment.uploaded = true + } + } +} + // create/update doc const docList = createListResource({ doctype: props.doctype, insert: { - onSuccess() { + async onSuccess(data) { toast({ title: "Success", text: `${props.doctype} created successfully!`, @@ -203,6 +246,8 @@ const docList = createListResource({ position: "bottom-center", iconClasses: "text-green-500", }) + + await uploadAllAttachments(data.doctype, data.name) router.back() }, onError() { diff --git a/frontend/src/composables/index.js b/frontend/src/composables/index.js new file mode 100644 index 0000000000..8313ae21c4 --- /dev/null +++ b/frontend/src/composables/index.js @@ -0,0 +1,38 @@ +import { createResource } from "frappe-ui" + +function getFileReader() { + const fileReader = new FileReader() + const zoneOriginalInstance = fileReader["__zone_symbol__originalInstance"] + return zoneOriginalInstance || fileReader +} + +export class FileAttachmentUploader { + constructor(fileObj) { + this.fileObj = fileObj + this.fileName = fileObj.name + } + + upload(documentType, documentName, fieldName, successHandler = () => {}) { + const reader = getFileReader() + const uploader = createResource({ + url: "hrms.api.upload_base64_file", + onSuccess: successHandler, + }) + + reader.onload = () => { + console.log("Loaded successfully ✅") + this.fileContents = reader.result.toString().split(",")[1] + + uploader.submit({ + content: this.fileContents, + dt: documentType, + dn: documentName, + filename: this.fileName, + fieldname: fieldName, + }) + } + reader.readAsDataURL(this.fileObj) + + return uploader + } +} diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index b59b1138d4..7c9b85b92f 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -9,6 +9,7 @@ :id="props.id" :tabbedView="true" :tabs="tabs" + :showAttachmentView="true" @validateForm="validateForm" > diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 32e24f318a..df39567fd1 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -405,3 +405,29 @@ def get_link_field_options(doctype: str) -> list: link_options = frappe.get_all(doctype, fields=fields) return link_options + + +@frappe.whitelist() +def upload_base64_file(content, filename, dt=None, dn=None, fieldname=None): + import base64 + from mimetypes import guess_type + + from frappe.handler import ALLOWED_MIMETYPES + + decoded_content = base64.b64decode(content) + content_type = guess_type(filename)[0] + if content_type not in ALLOWED_MIMETYPES: + frappe.throw("You can only upload JPG, PNG, PDF, TXT or Microsoft documents.") + + return frappe.get_doc( + { + "doctype": "File", + "attached_to_doctype": dt, + "attached_to_name": dn, + "attached_to_field": fieldname, + "folder": "Home", + "file_name": filename, + "content": decoded_content, + "is_private": 1, + } + ).insert() From 2c353c2105d6ea5010c518f1bd15785c216e7d74 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 8 Aug 2023 22:15:48 +0530 Subject: [PATCH 47/92] style: fix file uploader style --- frontend/src/components/ExpenseTaxesTable.vue | 2 +- frontend/src/components/FileUploaderView.vue | 68 ++++++++++--------- frontend/src/components/FormView.vue | 7 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/ExpenseTaxesTable.vue b/frontend/src/components/ExpenseTaxesTable.vue index b545027a82..dddc44fb28 100644 --- a/frontend/src/components/ExpenseTaxesTable.vue +++ b/frontend/src/components/ExpenseTaxesTable.vue @@ -1,6 +1,6 @@ @@ -108,6 +109,7 @@ v-if="showAttachmentView" v-model="fileAttachments" @handleFileSelect="handleFileSelect" + @handleFileDelete="handleFileDelete" /> @@ -146,11 +148,12 @@ import { createListResource, createDocumentResource, toast, + createResource, } from "frappe-ui" import FormField from "@/components/FormField.vue" import FileUploaderView from "@/components/FileUploaderView.vue" -import { FileAttachmentUploader } from "@/composables/index" +import { FileAttachment } from "@/composables/index" const props = defineProps({ doctype: { @@ -186,7 +189,7 @@ const props = defineProps({ }) const emit = defineEmits(["validateForm", "update:modelValue"]) const router = useRouter() -const activeTab = ref(props.tabs?.[0].name) +let activeTab = ref(props.tabs?.[0].name) let fileAttachments = ref([]) const formModel = computed({ @@ -216,13 +219,43 @@ const tabFields = computed(() => { return fieldsByTab }) +const attachedFiles = createResource({ + url: "hrms.api.get_attachments", + params: { + dt: props.doctype, + dn: props.id, + }, + transform(data) { + return data.map((file) => (file.uploaded = true)) + }, + onSuccess(data) { + fileAttachments.value = data + }, +}) + const handleFileSelect = (e) => { fileAttachments.value.push(...e.target.files) + + if (props.id) { + uploadAllAttachments(props.doctype, props.id) + } +} + +const handleFileDelete = async (fileObj) => { + if (fileObj.uploaded) { + const fileAttachment = new FileAttachment(fileObj) + await fileAttachment.delete() + await attachedFiles.reload() + } else { + fileAttachments.value = fileAttachments.value.filter( + (file) => file.name !== fileName + ) + } } const uploadAttachment = async (doctype, name, file) => { - const fileAttachmentUploader = new FileAttachmentUploader(file) - return fileAttachmentUploader.upload(doctype, name).promise + const fileAttachment = new FileAttachment(file) + return fileAttachment.upload(doctype, name).promise } async function uploadAllAttachments(documentType, documentName) { @@ -317,6 +350,7 @@ onMounted(async () => { if (props.id) { await documentResource.get.promise formModel.value = documentResource.doc + await attachedFiles.reload() } }) diff --git a/frontend/src/composables/index.js b/frontend/src/composables/index.js index 8313ae21c4..013631f861 100644 --- a/frontend/src/composables/index.js +++ b/frontend/src/composables/index.js @@ -6,7 +6,7 @@ function getFileReader() { return zoneOriginalInstance || fileReader } -export class FileAttachmentUploader { +export class FileAttachment { constructor(fileObj) { this.fileObj = fileObj this.fileName = fileObj.name @@ -35,4 +35,15 @@ export class FileAttachmentUploader { return uploader } + + delete() { + return createResource({ + url: "hrms.api.delete_attachment", + onSuccess: () => { + console.log("Deleted successfully ✅") + }, + }).submit({ + filename: this.fileName, + }) + } } diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 69a7303364..9e4829df78 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -413,6 +413,14 @@ def get_link_field_options(doctype: str, filters: dict | None = None) -> list: return link_options +# File +@frappe.whitelist() +def get_attachments(dt: str, dn: str): + from frappe.desk.form.load import get_attachments + + return get_attachments(dt, dn) + + @frappe.whitelist() def upload_base64_file(content, filename, dt=None, dn=None, fieldname=None): import base64 @@ -437,3 +445,8 @@ def upload_base64_file(content, filename, dt=None, dn=None, fieldname=None): "is_private": 1, } ).insert() + + +@frappe.whitelist() +def delete_attachment(filename: str): + frappe.delete_doc("File", filename) From 6ee42425024118aa956935c0a4bf11d2a112d641 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 15 Aug 2023 14:32:50 +0530 Subject: [PATCH 68/92] feat: show status indicator on saved doc --- frontend/src/components/FormView.vue | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index 32c442a287..643bdc084a 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -9,11 +9,18 @@ -
-

+
+

{{ doctype }}

- + +

{{ `New ${doctype}` }} From baff176fb0b41f5ad3d61786f051a3136d7214a6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 15 Aug 2023 16:32:30 +0530 Subject: [PATCH 69/92] refactor: move expense claim list config to doctype settings --- .../doctype/expense_claim/expense_claim.json | 29 +++++++++++++++++-- .../expense_claim/expense_claim_list.js | 12 -------- 2 files changed, 27 insertions(+), 14 deletions(-) delete mode 100644 hrms/hr/doctype/expense_claim/expense_claim_list.js diff --git a/hrms/hr/doctype/expense_claim/expense_claim.json b/hrms/hr/doctype/expense_claim/expense_claim.json index 60f285f70f..e97bddc61a 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.json +++ b/hrms/hr/doctype/expense_claim/expense_claim.json @@ -382,7 +382,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-14 15:31:49.277947", + "modified": "2023-08-15 15:31:49.277947", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", @@ -448,7 +448,32 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "states": [], + "states": [ + { + "color": "Gray", + "title": "Draft" + }, + { + "color": "Blue", + "title": "Submitted" + }, + { + "color": "Red", + "title": "Cancelled" + }, + { + "color": "Green", + "title": "Paid" + }, + { + "color": "Yellow", + "title": "Unpaid" + }, + { + "color": "Red", + "title": "Rejected" + } + ], "timeline_field": "employee", "title_field": "employee_name" } \ No newline at end of file diff --git a/hrms/hr/doctype/expense_claim/expense_claim_list.js b/hrms/hr/doctype/expense_claim/expense_claim_list.js deleted file mode 100644 index 9bafc18562..0000000000 --- a/hrms/hr/doctype/expense_claim/expense_claim_list.js +++ /dev/null @@ -1,12 +0,0 @@ -frappe.listview_settings['Expense Claim'] = { - add_fields: ["total_claimed_amount", "docstatus", "company"], - get_indicator: function(doc) { - if(doc.status == "Paid") { - return [__("Paid"), "green", "status,=,Paid"]; - }else if(doc.status == "Unpaid") { - return [__("Unpaid"), "orange", "status,=,Unpaid"]; - } else if(doc.status == "Rejected") { - return [__("Rejected"), "grey", "status,=,Rejected"]; - } - } -}; From a04cff329014a9441905aceb9687aa518333ce10 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 15 Aug 2023 16:33:09 +0530 Subject: [PATCH 70/92] feat: show colored status badge on saved form --- frontend/src/components/FormView.vue | 7 +++++++ hrms/api/__init__.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index 643bdc084a..4fc9365a2b 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -19,6 +19,7 @@

@@ -226,6 +227,12 @@ const tabFields = computed(() => { return fieldsByTab }) +const colorMap = createResource({ + url: "hrms.api.get_doctype_states", + params: { doctype: props.doctype }, + auto: true, +}) + const attachedFiles = createResource({ url: "hrms.api.get_attachments", params: { diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 9e4829df78..98a5cfca6e 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -401,6 +401,12 @@ def get_doctype_fields(doctype: str) -> list[dict]: return frappe.get_meta(doctype).fields +@frappe.whitelist() +def get_doctype_states(doctype: str) -> dict: + states = frappe.get_meta(doctype).states + return {state.title: state.color.lower() for state in states} + + @frappe.whitelist() def get_link_field_options(doctype: str, filters: dict | None = None) -> list: fields = ["name as value"] From d409413c5f508a2f258fb093a86697275e896df5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 18 Aug 2023 10:55:47 +0530 Subject: [PATCH 71/92] feat: guess status color if doctype states are not configured --- frontend/src/components/FormView.vue | 19 ++++++----- frontend/src/composables/index.js | 47 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index 4fc9365a2b..24c900d9f3 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -19,7 +19,7 @@ @@ -161,7 +161,7 @@ import { import FormField from "@/components/FormField.vue" import FileUploaderView from "@/components/FileUploaderView.vue" -import { FileAttachment } from "@/composables/index" +import { FileAttachment, guessStatusColor } from "@/composables/index" const props = defineProps({ doctype: { @@ -199,6 +199,7 @@ const emit = defineEmits(["validateForm", "update:modelValue"]) const router = useRouter() let activeTab = ref(props.tabs?.[0].name) let fileAttachments = ref([]) +let statusColor = ref("") const formModel = computed({ get() { @@ -227,12 +228,6 @@ const tabFields = computed(() => { return fieldsByTab }) -const colorMap = createResource({ - url: "hrms.api.get_doctype_states", - params: { doctype: props.doctype }, - auto: true, -}) - const attachedFiles = createResource({ url: "hrms.api.get_attachments", params: { @@ -354,6 +349,13 @@ function submitForm() { } } +async function setStatusColor() { + const status = formModel.value.status || formModel.value.approval_status + if (status) { + statusColor.value = await guessStatusColor(props.doctype, status) + } +} + const isFormReady = computed(() => { if (!props.id) return true @@ -365,6 +367,7 @@ onMounted(async () => { await documentResource.get.promise formModel.value = documentResource.doc await attachedFiles.reload() + await setStatusColor() } }) diff --git a/frontend/src/composables/index.js b/frontend/src/composables/index.js index 013631f861..6478e83157 100644 --- a/frontend/src/composables/index.js +++ b/frontend/src/composables/index.js @@ -47,3 +47,50 @@ export class FileAttachment { }) } } + +export async function guessStatusColor(doctype, status) { + const statesResource = createResource({ + url: "hrms.api.get_doctype_states", + params: { doctype: doctype }, + }) + + const stateMap = await statesResource.reload() + + if (stateMap?.length) { + return stateMap?.[status] + } + + let color = "gray" + + if ( + ["Open", "Pending", "Unpaid", "Review", "Medium", "Not Approved"].includes( + status + ) + ) { + color = "yellow" + } else if ( + ["Urgent", "High", "Failed", "Rejected", "Error"].includes(status) + ) { + color = "red" + } else if ( + [ + "Closed", + "Finished", + "Converted", + "Completed", + "Complete", + "Confirmed", + "Approved", + "Yes", + "Active", + "Available", + "Success", + ].includes(status) + ) { + color = "green" + } else if (status === "Submitted") { + color = "blue" + } + + return color +} From 692b039c3f12a562d54077e11220ae78b3b28fde Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 18 Aug 2023 17:21:05 +0530 Subject: [PATCH 72/92] fix: order lists by modified desc --- frontend/src/components/ListView.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue index 4162568edb..a9938ce33f 100644 --- a/frontend/src/components/ListView.vue +++ b/frontend/src/components/ListView.vue @@ -230,6 +230,7 @@ function fetchDocumentList() { fields: props.fields, filters: filters, group_by: props.groupBy, + order_by: `\`tab${props.doctype}\`.modified desc`, }) } From da419f46f1db763204a0885cba44203e08c90f19 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 18 Aug 2023 17:26:44 +0530 Subject: [PATCH 73/92] fix(form routing): re-route to saved form view on new record insertion --- frontend/src/components/FormView.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index 24c900d9f3..d76aff12b0 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -288,9 +288,12 @@ const docList = createListResource({ position: "bottom-center", iconClasses: "text-green-500", }) - await uploadAllAttachments(data.doctype, data.name) - router.back() + + router.replace({ + name: `${props.doctype.replace(/\s+/g, "")}DetailView`, + params: { id: data.name }, + }) }, onError() { console.log(`Error creating ${props.doctype}`) @@ -311,7 +314,6 @@ const documentResource = createDocumentResource({ position: "bottom-center", iconClasses: "text-green-500", }) - router.back() }, onError() { console.log(`Error updating ${props.doctype}`) From 9b5614e280474796e50b6994374ca6e5e1368f7e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 19 Aug 2023 12:35:29 +0530 Subject: [PATCH 74/92] feat: submit & cancel doc --- frontend/src/components/ExpenseTaxesTable.vue | 2 +- frontend/src/components/FormView.vue | 86 ++++++++++++++++--- frontend/src/views/expense_claim/Form.vue | 8 +- frontend/src/views/leave/Form.vue | 1 + .../hr/doctype/expense_claim/expense_claim.py | 2 +- 5 files changed, 86 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/ExpenseTaxesTable.vue b/frontend/src/components/ExpenseTaxesTable.vue index 219eb1bcac..ff98099e09 100644 --- a/frontend/src/components/ExpenseTaxesTable.vue +++ b/frontend/src/components/ExpenseTaxesTable.vue @@ -18,7 +18,7 @@
+
@@ -147,7 +150,7 @@ diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index 7521a59810..7838bd7335 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -5,6 +5,7 @@ v-if="formFields.data" doctype="Expense Claim" v-model="expenseClaim" + :isSubmittable="true" :fields="formFields.data" :id="props.id" :tabbedView="true" @@ -369,8 +370,13 @@ function setFormReadOnly() { function validateForm() { // set selected advances - expenseClaim.value.advances = expenseClaim.value.advances.filter( + if (!expenseClaim?.value?.advances) return + + expenseClaim.value.advances = expenseClaim?.value?.advances?.filter( (advance) => advance.selected ) + expenseClaim?.value?.expenses?.forEach((expense) => { + expense.cost_center = expenseClaim.value.cost_center + }) } diff --git a/frontend/src/views/leave/Form.vue b/frontend/src/views/leave/Form.vue index b04d864e7f..c75035a7ef 100644 --- a/frontend/src/views/leave/Form.vue +++ b/frontend/src/views/leave/Form.vue @@ -5,6 +5,7 @@ v-if="formFields.data" doctype="Leave Application" v-model="leaveApplication" + :isSubmittable="true" :fields="formFields.data" :id="props.id" @validateForm="validateForm" diff --git a/hrms/hr/doctype/expense_claim/expense_claim.py b/hrms/hr/doctype/expense_claim/expense_claim.py index b290449a74..9f96b9d24a 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.py +++ b/hrms/hr/doctype/expense_claim/expense_claim.py @@ -53,7 +53,7 @@ def set_status(self, update=False): # set as paid self.is_paid or ( - flt(self.total_sanctioned_amount > 0) + flt(self.total_sanctioned_amount) > 0 and ( # grand total is reimbursed ( From 900a370d89c08b7fea57275e17177d8aa0dcc754 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 19 Aug 2023 15:57:18 +0530 Subject: [PATCH 75/92] fix: set submitted/cancelled form as read-only --- .../src/components/ExpenseAdvancesTable.vue | 2 +- frontend/src/components/ExpenseTaxesTable.vue | 19 +++++++++++++------ frontend/src/components/ExpensesTable.vue | 19 +++++++++++++------ frontend/src/components/FormView.vue | 9 +++++++-- frontend/src/views/expense_claim/Form.vue | 16 +++++++++------- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/ExpenseAdvancesTable.vue b/frontend/src/components/ExpenseAdvancesTable.vue index c273b15292..85ae7c1b0d 100644 --- a/frontend/src/components/ExpenseAdvancesTable.vue +++ b/frontend/src/components/ExpenseAdvancesTable.vue @@ -57,7 +57,7 @@
- + diff --git a/frontend/src/data/currencies.js b/frontend/src/data/currencies.js index 31c690cac9..16e2995db4 100644 --- a/frontend/src/data/currencies.js +++ b/frontend/src/data/currencies.js @@ -11,7 +11,11 @@ const currencySymbols = createResource({ }) export function getCompanyCurrency(company) { - return companyCurrency?.data?.[company] + return companyCurrency?.data?.[company][0] +} + +export function getCompanyCurrencySymbol(company) { + return companyCurrency?.data?.[company][1] } export function getCurrencySymbol(currency) { diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue index 0b49103e4c..6e937ff7b7 100644 --- a/frontend/src/views/employee_advance/Form.vue +++ b/frontend/src/views/employee_advance/Form.vue @@ -1,11 +1,149 @@ diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue index b3cbe92abe..6d8a857c22 100644 --- a/frontend/src/views/expense_claim/Form.vue +++ b/frontend/src/views/expense_claim/Form.vue @@ -56,7 +56,7 @@ import ExpensesTable from "@/components/ExpensesTable.vue" import ExpenseTaxesTable from "@/components/ExpenseTaxesTable.vue" import ExpenseAdvancesTable from "@/components/ExpenseAdvancesTable.vue" -import { getCompanyCurrency } from "@/data/currencies" +import { getCompanyCurrencySymbol } from "@/data/currencies" const dayjs = inject("$dayjs") const employee = inject("$employee") @@ -82,7 +82,9 @@ const expenseClaim = ref({ company: employee.data.company, }) -const currency = computed(() => getCompanyCurrency(expenseClaim.value.company)) +const currency = computed(() => + getCompanyCurrencySymbol(expenseClaim.value.company) +) // get form fields const formFields = createResource({ diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 98a5cfca6e..e15ee766b8 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -360,6 +360,11 @@ def get_employee_advance_balance(employee: str) -> list[dict]: return advances +@frappe.whitelist() +def get_advance_account(company: str) -> str | None: + return frappe.db.get_value("Company", company, "default_employee_advance_account", cache=True) + + # Company @frappe.whitelist() def get_company_currencies() -> dict: @@ -373,12 +378,13 @@ def get_company_currencies() -> dict: .select( Company.name, Company.default_currency, - Currency.symbol, + Currency.name.as_("currency"), + Currency.symbol.as_("symbol"), ) ) companies = query.run(as_dict=True) - return {company.name: company.symbol or company.default_currency for company in companies} + return {company.name: (company.default_currency, company.symbol) for company in companies} @frappe.whitelist() From a50e133f8ffebd11e6f695b10449ebd9c59b5220 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 20 Aug 2023 02:17:05 +0530 Subject: [PATCH 79/92] feat: employee advance list view --- .../src/components/EmployeeAdvanceItem.vue | 8 ++- .../src/components/ListFiltersActionSheet.vue | 18 +++--- frontend/src/components/ListView.vue | 2 + frontend/src/views/employee_advance/List.vue | 61 ++++++++++++++++++- 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/EmployeeAdvanceItem.vue b/frontend/src/components/EmployeeAdvanceItem.vue index 5d518fe2ba..ff50065183 100644 --- a/frontend/src/components/EmployeeAdvanceItem.vue +++ b/frontend/src/components/EmployeeAdvanceItem.vue @@ -4,12 +4,18 @@
-
+
{{ `${currency} ${props.doc.balance_amount} /` }} {{ `${currency} ${props.doc.paid_amount}` }}
+
+ {{ `${currency} ${props.doc.advance_amount}` }} +
{{ props.doc.purpose }} diff --git a/frontend/src/components/ListFiltersActionSheet.vue b/frontend/src/components/ListFiltersActionSheet.vue index e0c299fcc9..5c242a7d36 100644 --- a/frontend/src/components/ListFiltersActionSheet.vue +++ b/frontend/src/components/ListFiltersActionSheet.vue @@ -18,7 +18,7 @@
{{ filter.label }}
-
+
diff --git a/frontend/src/views/leave/Dashboard.vue b/frontend/src/views/leave/Dashboard.vue index 0cc8e5e177..f01d88de5f 100644 --- a/frontend/src/views/leave/Dashboard.vue +++ b/frontend/src/views/leave/Dashboard.vue @@ -16,7 +16,8 @@
Recent Leaves
@@ -29,21 +30,22 @@ diff --git a/frontend/src/components/RequestActionSheet.vue b/frontend/src/components/RequestActionSheet.vue index 2dbeccf275..06e17fa9ee 100644 --- a/frontend/src/components/RequestActionSheet.vue +++ b/frontend/src/components/RequestActionSheet.vue @@ -1,7 +1,12 @@ @@ -47,7 +54,10 @@ import { ref } from "vue" import { IonModal } from "@ionic/vue" import RequestActionSheet from "@/components/RequestActionSheet.vue" -import { LEAVE_FIELDS } from "@/data/config/requestSummaryFields" +import { + LEAVE_FIELDS, + EXPENSE_CLAIM_FIELDS, +} from "@/data/config/requestSummaryFields" const props = defineProps({ component: { diff --git a/frontend/src/data/config/requestSummaryFields.js b/frontend/src/data/config/requestSummaryFields.js index 1c0ce6f93f..c2d6772efe 100644 --- a/frontend/src/data/config/requestSummaryFields.js +++ b/frontend/src/data/config/requestSummaryFields.js @@ -48,3 +48,56 @@ export const LEAVE_FIELDS = [ fieldtype: "Small Text", }, ] + +export const EXPENSE_CLAIM_FIELDS = [ + { + fieldname: "posting_date", + label: "Posting Date", + fieldtype: "Date", + }, + { + fieldname: "employee", + label: "Employee", + fieldtype: "Link", + }, + { + fieldname: "expenses", + label: "Expenses", + fieldtype: "Table", + }, + { + fieldname: "total_claimed_amount", + label: "Total Claimed Amount", + fieldtype: "Currency", + }, + { + fieldname: "total_sanctioned_amount", + label: "Total Sanctioned Amount", + fieldtype: "Currency", + }, + { + fieldname: "total_taxes_and_charges", + label: "Total Taxes and Charges", + fieldtype: "Currency", + }, + { + fieldname: "total_advance_amount", + label: "Total Advance Amount", + fieldtype: "Currency", + }, + { + fieldname: "grand_total", + label: "Grand Total", + fieldtype: "Currency", + }, + { + fieldname: "status", + label: "Status", + fieldtype: "Select", + }, + { + fieldname: "approval_status", + label: "Approval Status", + fieldtype: "Select", + }, +] diff --git a/frontend/src/data/currencies.js b/frontend/src/data/currencies.js index 16e2995db4..e95d3f99b1 100644 --- a/frontend/src/data/currencies.js +++ b/frontend/src/data/currencies.js @@ -11,11 +11,11 @@ const currencySymbols = createResource({ }) export function getCompanyCurrency(company) { - return companyCurrency?.data?.[company][0] + return companyCurrency?.data?.[company]?.[0] } export function getCompanyCurrencySymbol(company) { - return companyCurrency?.data?.[company][1] + return companyCurrency?.data?.[company]?.[1] } export function getCurrencySymbol(currency) { diff --git a/frontend/src/data/leaves.js b/frontend/src/data/leaves.js index 14b6d45163..bebe894b3f 100644 --- a/frontend/src/data/leaves.js +++ b/frontend/src/data/leaves.js @@ -1,13 +1,25 @@ import { createResource } from "frappe-ui" import { employeeResource } from "./employee" +import dayjs from "@/utils/dayjs" + const transformLeaveData = (data) => { return data.map((leave) => { + leave.leave_dates = get_leave_dates(leave) leave.doctype = "Leave Application" return leave }) } +const get_leave_dates = (leave) => { + if (leave.from_date == leave.to_date) + return dayjs(leave.from_date).format("D MMM") + else + return `${dayjs(leave.from_date).format("D MMM")} - ${dayjs( + leave.to_date + ).format("D MMM")}` +} + export const myLeaves = createResource({ url: "hrms.api.get_employee_leave_applications", params: { From a4fad8d0e35711a10d5256b06a42533b37c77306 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 20 Aug 2023 17:48:37 +0530 Subject: [PATCH 83/92] feat: Expense Claim Request Action Sheet --- frontend/src/components/ExpenseItems.vue | 55 +++++++++++++++++++ .../src/components/RequestActionSheet.vue | 52 ++++++++++++++++-- .../src/data/config/requestSummaryFields.js | 11 ++++ 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/ExpenseItems.vue diff --git a/frontend/src/components/ExpenseItems.vue b/frontend/src/components/ExpenseItems.vue new file mode 100644 index 0000000000..364df06069 --- /dev/null +++ b/frontend/src/components/ExpenseItems.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/components/RequestActionSheet.vue b/frontend/src/components/RequestActionSheet.vue index 06e17fa9ee..57c34f4cc5 100644 --- a/frontend/src/components/RequestActionSheet.vue +++ b/frontend/src/components/RequestActionSheet.vue @@ -14,20 +14,46 @@ v-for="field in fieldsWithValues" :key="field.fieldname" :class="[ - ['Small Text', 'Text', 'Long Text'].includes(field.fieldtype) + ['Small Text', 'Text', 'Long Text', 'Table'].includes(field.fieldtype) ? 'flex-col' : 'flex-row items-center justify-between', 'flex w-full', ]" >
{{ field.label }}
+
+ +
+
Attachments
+ +
+
From 2d2dab651fc0b2268738d180fe32ba5763ba6baf Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 20 Aug 2023 20:25:04 +0530 Subject: [PATCH 85/92] refactor: Approval/Rejection -> Submission/Cancellation flow - don't implicitly submit doc, approve and add a separate action for submission --- .../src/components/RequestActionSheet.vue | 82 +++++++++++++++---- frontend/src/components/RequestList.vue | 2 +- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/RequestActionSheet.vue b/frontend/src/components/RequestActionSheet.vue index 821ee7926c..bf5b17ceb5 100644 --- a/frontend/src/components/RequestActionSheet.vue +++ b/frontend/src/components/RequestActionSheet.vue @@ -3,7 +3,9 @@ v-if="document?.doc" class="bg-white w-full flex flex-col items-center justify-center pb-5" > -
+
{{ document?.doctype }} @@ -64,30 +66,47 @@
+ +
+ +
+
-
+

{{ doctype }}

- + + +

@@ -162,6 +186,7 @@ import { createDocumentResource, toast, createResource, + Dropdown, } from "frappe-ui" import FormField from "@/components/FormField.vue" import FileUploaderView from "@/components/FileUploaderView.vue" @@ -302,7 +327,7 @@ async function uploadAllAttachments(documentType, documentName) { } } -// create/update doc +// CRUD for doc const docList = createListResource({ doctype: props.doctype, insert: { @@ -345,6 +370,21 @@ const documentResource = createDocumentResource({ console.log(`Error updating ${props.doctype}`) }, }, + delete: { + onSuccess() { + router.back() + toast({ + title: "Success", + text: `${props.doctype} deleted successfully!`, + icon: "check-circle", + position: "bottom-center", + iconClasses: "text-green-500", + }) + }, + onError() { + console.log(`Error deleting ${props.doctype}`) + }, + }, }) const docPermissions = createResource({ @@ -375,6 +415,10 @@ const formButton = computed(() => { } }) +function showDeleteButton() { + return props.id && formModel.value.docstatus !== 1 && hasPermission("delete") +} + function hasPermission(action) { return docPermissions.data?.permissions[action] } @@ -426,6 +470,14 @@ function submitOrCancelForm() { } } +function handleDocDelete() { + documentResource.delete.submit() +} + +function handleDocReload() { + documentResource.reload() +} + async function setStatusColor() { const status = formModel.value.status || formModel.value.approval_status if (status) { From 56dfe73b73a76596d5c99f67a31254acc5bd3c0a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 21 Aug 2023 16:28:11 +0530 Subject: [PATCH 89/92] fix: confirm before delete, submit, cancel operations --- frontend/src/components/FormView.vue | 93 +++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue index ccde9fb467..1454d3296d 100644 --- a/frontend/src/components/FormView.vue +++ b/frontend/src/components/FormView.vue @@ -36,7 +36,7 @@ { label: 'Delete', condition: showDeleteButton, - handler: () => handleDocDelete(), + handler: () => showDeleteDialog = true, }, { label: 'Reload', handler: () => handleDocReload() }, ]" @@ -173,6 +173,68 @@

+ + + + + + + diff --git a/frontend/src/components/RequestPanel.vue b/frontend/src/components/RequestPanel.vue index 168dd75166..a4b98cb342 100644 --- a/frontend/src/components/RequestPanel.vue +++ b/frontend/src/components/RequestPanel.vue @@ -14,7 +14,7 @@ diff --git a/frontend/src/data/leaves.js b/frontend/src/data/leaves.js index bebe894b3f..bdb10062c7 100644 --- a/frontend/src/data/leaves.js +++ b/frontend/src/data/leaves.js @@ -5,13 +5,13 @@ import dayjs from "@/utils/dayjs" const transformLeaveData = (data) => { return data.map((leave) => { - leave.leave_dates = get_leave_dates(leave) + leave.leave_dates = getLeaveDates(leave) leave.doctype = "Leave Application" return leave }) } -const get_leave_dates = (leave) => { +export const getLeaveDates = (leave) => { if (leave.from_date == leave.to_date) return dayjs(leave.from_date).format("D MMM") else diff --git a/frontend/src/views/expense_claim/Dashboard.vue b/frontend/src/views/expense_claim/Dashboard.vue index 2251217d6b..43d4144272 100644 --- a/frontend/src/views/expense_claim/Dashboard.vue +++ b/frontend/src/views/expense_claim/Dashboard.vue @@ -47,7 +47,7 @@ diff --git a/frontend/src/views/leave/Dashboard.vue b/frontend/src/views/leave/Dashboard.vue index f01d88de5f..9aeb180bfe 100644 --- a/frontend/src/views/leave/Dashboard.vue +++ b/frontend/src/views/leave/Dashboard.vue @@ -30,7 +30,7 @@