-
-
-
-
-
diff --git a/frontend/src/components/RequestList.vue b/frontend/src/components/RequestList.vue
index b4b56e4486..b6485167d0 100644
--- a/frontend/src/components/RequestList.vue
+++ b/frontend/src/components/RequestList.vue
@@ -1,7 +1,7 @@
-
- You have no requests
-
+
-
+
diff --git a/frontend/src/components/icons/ExpenseIcon.vue b/frontend/src/components/icons/ExpenseIcon.vue
new file mode 100644
index 0000000000..f62397ef3c
--- /dev/null
+++ b/frontend/src/components/icons/ExpenseIcon.vue
@@ -0,0 +1,18 @@
+
+
+
diff --git a/frontend/src/components/icons/WalletIcon.vue b/frontend/src/components/icons/WalletIcon.vue
new file mode 100644
index 0000000000..50a1ee851a
--- /dev/null
+++ b/frontend/src/components/icons/WalletIcon.vue
@@ -0,0 +1,14 @@
+
+
+
diff --git a/frontend/src/composables/index.js b/frontend/src/composables/index.js
new file mode 100644
index 0000000000..6478e83157
--- /dev/null
+++ b/frontend/src/composables/index.js
@@ -0,0 +1,96 @@
+import { createResource } from "frappe-ui"
+
+function getFileReader() {
+ const fileReader = new FileReader()
+ const zoneOriginalInstance = fileReader["__zone_symbol__originalInstance"]
+ return zoneOriginalInstance || fileReader
+}
+
+export class FileAttachment {
+ 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
+ }
+
+ delete() {
+ return createResource({
+ url: "hrms.api.delete_attachment",
+ onSuccess: () => {
+ console.log("Deleted successfully ✅")
+ },
+ }).submit({
+ filename: this.fileName,
+ })
+ }
+}
+
+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
+}
diff --git a/frontend/src/data/advances.js b/frontend/src/data/advances.js
new file mode 100644
index 0000000000..73dacd897e
--- /dev/null
+++ b/frontend/src/data/advances.js
@@ -0,0 +1,20 @@
+import { createResource } from "frappe-ui"
+import { employeeResource } from "./employee"
+
+const transformAdvanceData = (data) => {
+ return data.map((claim) => {
+ claim.doctype = "Employee Advance"
+ return claim
+ })
+}
+
+export const advanceBalance = createResource({
+ url: "hrms.api.get_employee_advance_balance",
+ params: {
+ employee: employeeResource.data.name,
+ },
+ auto: true,
+ transform(data) {
+ return transformAdvanceData(data)
+ },
+})
diff --git a/frontend/src/data/claims.js b/frontend/src/data/claims.js
new file mode 100644
index 0000000000..18e8298af0
--- /dev/null
+++ b/frontend/src/data/claims.js
@@ -0,0 +1,49 @@
+import { createResource } from "frappe-ui"
+import { employeeResource } from "./employee"
+import { reactive } from "vue"
+
+const transformClaimData = (data) => {
+ return data.map((claim) => {
+ claim.doctype = "Expense Claim"
+ return claim
+ })
+}
+
+export const myClaims = createResource({
+ url: "hrms.api.get_expense_claims",
+ params: {
+ employee: employeeResource.data.name,
+ limit: 5,
+ },
+ auto: true,
+ transform(data) {
+ return transformClaimData(data)
+ },
+})
+
+export const teamClaims = 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)
+ },
+})
+
+export let claimTypesByID = reactive({})
+
+export const claimTypesResource = createResource({
+ url: "hrms.api.get_expense_claim_types",
+ auto: true,
+ transform(data) {
+ return data.map((row) => {
+ claimTypesByID[row.name] = row
+ return row
+ })
+ },
+})
diff --git a/frontend/src/data/config/requestSummaryFields.js b/frontend/src/data/config/requestSummaryFields.js
index 1c0ce6f93f..8db45e4843 100644
--- a/frontend/src/data/config/requestSummaryFields.js
+++ b/frontend/src/data/config/requestSummaryFields.js
@@ -2,6 +2,11 @@
// TODO: This should be config-driven somehow
export const LEAVE_FIELDS = [
+ {
+ fieldname: "name",
+ label: "ID",
+ fieldtype: "Data",
+ },
{
fieldname: "leave_type",
label: "Leave Type",
@@ -48,3 +53,62 @@ export const LEAVE_FIELDS = [
fieldtype: "Small Text",
},
]
+
+export const EXPENSE_CLAIM_FIELDS = [
+ {
+ fieldname: "name",
+ label: "ID",
+ fieldtype: "Data",
+ },
+ {
+ fieldname: "posting_date",
+ label: "Posting Date",
+ fieldtype: "Date",
+ },
+ {
+ fieldname: "employee",
+ label: "Employee",
+ fieldtype: "Link",
+ },
+ {
+ fieldname: "expenses",
+ label: "Expenses",
+ fieldtype: "Table",
+ componentName: "ExpenseItems",
+ },
+ {
+ 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
new file mode 100644
index 0000000000..e95d3f99b1
--- /dev/null
+++ b/frontend/src/data/currencies.js
@@ -0,0 +1,23 @@
+import { createResource } from "frappe-ui"
+
+const companyCurrency = createResource({
+ url: "hrms.api.get_company_currencies",
+ auto: true,
+})
+
+const currencySymbols = createResource({
+ url: "hrms.api.get_currency_symbols",
+ auto: true,
+})
+
+export function getCompanyCurrency(company) {
+ return companyCurrency?.data?.[company]?.[0]
+}
+
+export function getCompanyCurrencySymbol(company) {
+ return companyCurrency?.data?.[company]?.[1]
+}
+
+export function getCurrencySymbol(currency) {
+ return currencySymbols?.data?.[currency]
+}
diff --git a/frontend/src/data/leaves.js b/frontend/src/data/leaves.js
index bb26dce14d..bdb10062c7 100644
--- a/frontend/src/data/leaves.js
+++ b/frontend/src/data/leaves.js
@@ -1,18 +1,17 @@
import { createResource } from "frappe-ui"
-import { computed } from "vue"
import { employeeResource } from "./employee"
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
@@ -21,10 +20,11 @@ const get_leave_dates = (leave) => {
).format("D MMM")}`
}
-export const myRequests = createResource({
+export const myLeaves = createResource({
url: "hrms.api.get_employee_leave_applications",
params: {
employee: employeeResource.data.name,
+ limit: 5,
},
auto: true,
transform(data) {
@@ -32,24 +32,15 @@ export const myRequests = createResource({
},
})
-export const teamRequests = createResource({
+export const teamLeaves = createResource({
url: "hrms.api.get_team_leave_applications",
params: {
employee: employeeResource.data.name,
user_id: employeeResource.data.user_id,
+ limit: 5,
},
auto: true,
transform(data) {
return transformLeaveData(data)
},
})
-
-export const leavesThisMonth = computed(() => {
- const today = dayjs()
- const start = today.startOf("month").format("YYYY-MM-DD")
- const end = today.endOf("month").format("YYYY-MM-DD")
-
- return myRequests.data?.filter((leave) => {
- return dayjs(leave.from_date).isBetween(start, end, null, "[]")
- })
-})
diff --git a/frontend/src/main.css b/frontend/src/main.css
index f5641a04d6..d4d22bd462 100644
--- a/frontend/src/main.css
+++ b/frontend/src/main.css
@@ -4,3 +4,8 @@
ion-modal {
--height: auto;
}
+
+input:disabled {
+ --webkit-text-fill-color: var(--text-color);
+ opacity: 1; /* required on iOS */
+}
diff --git a/frontend/src/main.js b/frontend/src/main.js
index feda9248c6..6d5862048e 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -3,13 +3,20 @@ 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 EmptyState from "@/components/EmptyState.vue"
import { IonicVue } from "@ionic/vue"
-import { session } from "./data/session"
-import { userResource } from "./data/user"
-import { employeeResource } from "./data/employee"
+import { session } from "@/data/session"
+import { userResource } from "@/data/user"
+import { employeeResource } from "@/data/employee"
import dayjs from "@/utils/dayjs"
/* Core CSS required for Ionic components to work properly */
@@ -26,6 +33,8 @@ setConfig("resourceFetcher", frappeRequest)
app.use(resourcesPlugin)
app.component("Button", Button)
+app.component("Input", Input)
+app.component("EmptyState", EmptyState)
app.use(router)
app.use(IonicVue)
diff --git a/frontend/src/router/advances.js b/frontend/src/router/advances.js
new file mode 100644
index 0000000000..174b9512bc
--- /dev/null
+++ b/frontend/src/router/advances.js
@@ -0,0 +1,20 @@
+const routes = [
+ {
+ name: "EmployeeAdvanceListView",
+ path: "/employee-advance/list",
+ component: () => import("@/views/employee_advance/List.vue"),
+ },
+ {
+ name: "EmployeeAdvanceFormView",
+ path: "/employee-advance/new",
+ component: () => import("@/views/employee_advance/Form.vue"),
+ },
+ {
+ name: "EmployeeAdvanceDetailView",
+ path: "/employee-advance/:id",
+ props: true,
+ component: () => import("@/views/employee_advance/Form.vue"),
+ },
+]
+
+export default routes
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.js b/frontend/src/router/index.js
similarity index 84%
rename from frontend/src/router.js
rename to frontend/src/router/index.js
index a37b32988e..02f1a0e077 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router/index.js
@@ -2,6 +2,10 @@ import { createRouter, createWebHistory } from "@ionic/vue-router"
import { session } from "@/data/session"
import { userResource } from "@/data/user"
+import leaveRoutes from "./leaves"
+import claimRoutes from "./claims"
+import employeeAdvanceRoutes from "./advances"
+
const routes = [
{
path: "/",
@@ -18,16 +22,14 @@ 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,
+ ...claimRoutes,
+ ...employeeAdvanceRoutes,
]
const router = createRouter({
diff --git a/frontend/src/router/leaves.js b/frontend/src/router/leaves.js
new file mode 100644
index 0000000000..16620ab60f
--- /dev/null
+++ b/frontend/src/router/leaves.js
@@ -0,0 +1,25 @@
+const routes = [
+ {
+ name: "Leaves",
+ path: "/leave/dashboard",
+ component: () => import("@/views/leave/Dashboard.vue"),
+ },
+ {
+ name: "LeaveApplicationListView",
+ path: "/leave/list",
+ component: () => import("@/views/leave/List.vue"),
+ },
+ {
+ name: "LeaveApplicationFormView",
+ path: "/leave/new",
+ component: () => import("@/views/leave/Form.vue"),
+ },
+ {
+ name: "LeaveApplicationDetailView",
+ path: "/leave/:id",
+ props: true,
+ component: () => import("@/views/leave/Form.vue"),
+ },
+]
+
+export default routes
diff --git a/frontend/src/socket.js b/frontend/src/socket.js
index 9185c60c33..8522bb632f 100644
--- a/frontend/src/socket.js
+++ b/frontend/src/socket.js
@@ -1,4 +1,14 @@
-import { initSocket } from "frappe-ui"
+import { io } from "socket.io-client"
+import { socketio_port } from "../../../../sites/common_site_config.json"
+
+function initSocket() {
+ let host = window.location.hostname
+ let port = window.location.port ? `:${socketio_port}` : ""
+ let protocol = port ? "http" : "https"
+ let url = `${protocol}://${host}${port}/${host}`
+ let socket = io(url, { withCredentials: true })
+ return socket
+}
const socket = initSocket()
export default socket
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index 5281fc4a46..2f19f61c92 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -25,18 +25,22 @@ const quickLinks = [
{
icon: "calendar",
title: "Request Leave",
+ route: "LeaveApplicationFormView",
},
{
icon: "dollar-sign",
title: "Claim an Expense",
+ route: "ExpenseClaimFormView",
},
{
icon: "file",
title: "Payslip",
+ route: "Home",
},
{
icon: "hard-drive",
title: "Documents",
+ route: "Home",
},
]
diff --git a/frontend/src/views/Leaves.vue b/frontend/src/views/Leaves.vue
deleted file mode 100644
index fab68d6a77..0000000000
--- a/frontend/src/views/Leaves.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
- Request a Leave
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/views/employee_advance/Form.vue b/frontend/src/views/employee_advance/Form.vue
new file mode 100644
index 0000000000..6e937ff7b7
--- /dev/null
+++ b/frontend/src/views/employee_advance/Form.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/employee_advance/List.vue b/frontend/src/views/employee_advance/List.vue
new file mode 100644
index 0000000000..188d0d4863
--- /dev/null
+++ b/frontend/src/views/employee_advance/List.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/expense_claim/Dashboard.vue b/frontend/src/views/expense_claim/Dashboard.vue
new file mode 100644
index 0000000000..43d4144272
--- /dev/null
+++ b/frontend/src/views/expense_claim/Dashboard.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+ Claim an Expense
+
+
+
+
+
+
+
+
+
+ Employee Advance Balance
+
+
+ View List
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/expense_claim/Form.vue b/frontend/src/views/expense_claim/Form.vue
new file mode 100644
index 0000000000..6958728069
--- /dev/null
+++ b/frontend/src/views/expense_claim/Form.vue
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/expense_claim/List.vue b/frontend/src/views/expense_claim/List.vue
new file mode 100644
index 0000000000..a904da379a
--- /dev/null
+++ b/frontend/src/views/expense_claim/List.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/leave/Dashboard.vue b/frontend/src/views/leave/Dashboard.vue
new file mode 100644
index 0000000000..9aeb180bfe
--- /dev/null
+++ b/frontend/src/views/leave/Dashboard.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+ Request a Leave
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/leave/Form.vue b/frontend/src/views/leave/Form.vue
new file mode 100644
index 0000000000..c75035a7ef
--- /dev/null
+++ b/frontend/src/views/leave/Form.vue
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/leave/List.vue b/frontend/src/views/leave/List.vue
new file mode 100644
index 0000000000..85f23cd2dc
--- /dev/null
+++ b/frontend/src/views/leave/List.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
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==
diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py
index f8d458f814..cf4cccaa41 100644
--- a/hrms/api/__init__.py
+++ b/hrms/api/__init__.py
@@ -1,5 +1,7 @@
import frappe
+from frappe import _
from frappe.query_builder import Order
+from frappe.query_builder.functions import Count
from frappe.utils import getdate
@@ -23,7 +25,7 @@ def get_current_employee_info() -> dict:
return employee
-@frappe.whitelist()
+# Leaves and Holidays
def get_leave_applications(filters: dict) -> list[dict]:
doctype = "Leave Application"
leave_applications = frappe.get_list(
@@ -104,7 +106,7 @@ def get_leave_balance_map(employee: str) -> dict[str, dict[str, float]]:
def get_holidays_for_employee(employee: str) -> list[dict]:
from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee
- holiday_list = get_holiday_list_for_employee(employee)
+ holiday_list = get_holiday_list_for_employee(employee, raise_exception=False)
if not holiday_list:
return []
@@ -115,3 +117,349 @@ 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_leave_approval_details(employee: str) -> dict:
+ leave_approver, department = frappe.get_cached_value(
+ "Employee",
+ employee,
+ ["leave_approver", "department"],
+ )
+
+ if not leave_approver and department:
+ leave_approver = frappe.db.get_value(
+ "Department Approver",
+ {"parent": department, "parentfield": "leave_approvers", "idx": 1},
+ "approver",
+ )
+
+ leave_approver_name = frappe.db.get_value("User", leave_approver, "full_name", cache=True)
+ department_approvers = get_department_approvers(department, "leave_approvers")
+
+ if leave_approver and leave_approver not in [approver.name for approver in department_approvers]:
+ department_approvers.append({"name": leave_approver, "full_name": leave_approver_name})
+
+ return dict(
+ leave_approver=leave_approver,
+ leave_approver_name=leave_approver_name,
+ department_approvers=department_approvers,
+ is_mandatory=frappe.db.get_single_value(
+ "HR Settings", "leave_approver_mandatory_in_leave_application"
+ ),
+ )
+
+
+def get_department_approvers(department: str, parentfield: str) -> list[str]:
+ if not department:
+ return []
+
+ department_details = frappe.db.get_value("Department", department, ["lft", "rgt"], as_dict=True)
+ departments = frappe.get_all(
+ "Department",
+ filters={
+ "lft": ("<=", department_details.lft),
+ "rgt": (">=", department_details.rgt),
+ "disabled": 0,
+ },
+ pluck="name",
+ )
+
+ Approver = frappe.qb.DocType("Department Approver")
+ User = frappe.qb.DocType("User")
+ department_approvers = (
+ frappe.qb.from_(User)
+ .join(Approver)
+ .on(Approver.approver == User.name)
+ .select(User.name.as_("name"), User.full_name.as_("full_name"))
+ .where((Approver.parent.isin(departments)) & (Approver.parentfield == parentfield))
+ ).run(as_dict=True)
+
+ return department_approvers
+
+
+@frappe.whitelist()
+def get_leave_types(employee: str, date: str) -> list:
+ from hrms.hr.doctype.leave_application.leave_application import get_leave_details
+
+ date = date or getdate()
+
+ leave_details = get_leave_details(employee, date)
+ leave_types = list(leave_details["leave_allocation"].keys()) + leave_details["lwps"]
+
+ return leave_types
+
+
+# Expense Claims
+@frappe.whitelist()
+def get_expense_claims(
+ employee: str,
+ approver_id: str = None,
+ for_approval: bool = False,
+ limit: int | None = None,
+) -> list[dict]:
+ Claim = frappe.qb.DocType("Expense Claim")
+ ClaimDetail = frappe.qb.DocType("Expense Claim Detail")
+
+ query = (
+ frappe.qb.from_(Claim)
+ .join(ClaimDetail)
+ .on(Claim.name == ClaimDetail.parent)
+ .select(
+ Claim.name,
+ Claim.employee,
+ Claim.employee_name,
+ Claim.approval_status,
+ Claim.status,
+ Claim.expense_approver,
+ Claim.total_claimed_amount,
+ Claim.posting_date,
+ Claim.company,
+ ClaimDetail.expense_type,
+ Count(ClaimDetail.expense_type).as_("total_expenses"),
+ )
+ .orderby(Claim.posting_date, order=Order.desc)
+ )
+
+ if for_approval:
+ query = query.where(
+ (Claim.docstatus == 0)
+ & (Claim.status == "Draft")
+ & (Claim.expense_approver == approver_id)
+ & (Claim.employee != employee)
+ )
+ else:
+ query = query.where((Claim.docstatus != 2) & (Claim.employee == employee))
+
+ if limit:
+ query = query.limit(limit)
+
+ query = query.groupby(Claim.name)
+ claims = query.run(as_dict=True)
+ return 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
+
+
+@frappe.whitelist()
+def get_expense_type_description(expense_type: str) -> str:
+ return frappe.db.get_value("Expense Claim Type", expense_type, "description")
+
+
+@frappe.whitelist()
+def get_expense_claim_types() -> list[dict]:
+ ClaimType = frappe.qb.DocType("Expense Claim Type")
+
+ return (frappe.qb.from_(ClaimType).select(ClaimType.name, ClaimType.description)).run(
+ as_dict=True
+ )
+
+
+@frappe.whitelist()
+def get_expense_approval_details(employee: str) -> dict:
+ expense_approver, department = frappe.get_cached_value(
+ "Employee",
+ employee,
+ ["expense_approver", "department"],
+ )
+
+ if not expense_approver and department:
+ expense_approver = frappe.db.get_value(
+ "Department Approver",
+ {"parent": department, "parentfield": "expense_approvers", "idx": 1},
+ "approver",
+ )
+
+ expense_approver_name = frappe.db.get_value("User", expense_approver, "full_name", cache=True)
+ department_approvers = get_department_approvers(department, "expense_approvers")
+
+ if expense_approver and expense_approver not in [
+ approver.name for approver in department_approvers
+ ]:
+ department_approvers.append({"name": expense_approver, "full_name": expense_approver_name})
+
+ return dict(
+ expense_approver=expense_approver,
+ expense_approver_name=expense_approver_name,
+ department_approvers=department_approvers,
+ is_mandatory=frappe.db.get_single_value(
+ "HR Settings", "expense_approver_mandatory_in_expense_claim"
+ ),
+ )
+
+
+# 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_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:
+ Company = frappe.qb.DocType("Company")
+ Currency = frappe.qb.DocType("Currency")
+
+ query = (
+ frappe.qb.from_(Company)
+ .join(Currency)
+ .on(Company.default_currency == Currency.name)
+ .select(
+ Company.name,
+ Company.default_currency,
+ Currency.name.as_("currency"),
+ Currency.symbol.as_("symbol"),
+ )
+ )
+
+ companies = query.run(as_dict=True)
+ return {company.name: (company.default_currency, company.symbol) 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}
+
+
+@frappe.whitelist()
+def get_company_cost_center(company: str) -> str:
+ return frappe.db.get_value("Company", company, "cost_center")
+
+
+# Form View APIs
+@frappe.whitelist()
+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"]
+ title_field = frappe.db.get_value("DocType", doctype, "title_field", cache=1)
+
+ if title_field:
+ fields.append(f"{title_field} as label")
+
+ link_options = frappe.get_all(doctype, fields=fields, filters=filters)
+ 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
+ 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()
+
+
+@frappe.whitelist()
+def delete_attachment(filename: str):
+ frappe.delete_doc("File", filename)
diff --git a/hrms/hr/doctype/employee_advance/employee_advance.js b/hrms/hr/doctype/employee_advance/employee_advance.js
index 0a88833caf..ee24ddc900 100644
--- a/hrms/hr/doctype/employee_advance/employee_advance.js
+++ b/hrms/hr/doctype/employee_advance/employee_advance.js
@@ -3,9 +3,6 @@
frappe.ui.form.on('Employee Advance', {
setup: function(frm) {
- frm.add_fetch("employee", "company", "company");
- frm.add_fetch("company", "default_employee_advance_account", "advance_account");
-
frm.set_query("employee", function() {
return {
filters: {
diff --git a/hrms/hr/doctype/employee_advance/employee_advance.json b/hrms/hr/doctype/employee_advance/employee_advance.json
index 29df92c049..903b4fb5bc 100644
--- a/hrms/hr/doctype/employee_advance/employee_advance.json
+++ b/hrms/hr/doctype/employee_advance/employee_advance.json
@@ -10,12 +10,14 @@
"naming_series",
"employee",
"employee_name",
- "department",
"column_break_4",
"posting_date",
+ "company",
+ "department",
+ "currency_section",
"currency",
+ "column_break_crso",
"exchange_rate",
- "repay_unclaimed_amount_from_salary",
"section_break_8",
"purpose",
"column_break_11",
@@ -25,12 +27,15 @@
"claimed_amount",
"return_amount",
"section_break_7",
- "status",
- "company",
- "amended_from",
"column_break_18",
"advance_account",
- "mode_of_payment"
+ "mode_of_payment",
+ "column_break_nhlv",
+ "repay_unclaimed_amount_from_salary",
+ "more_info_section",
+ "status",
+ "column_break_kimx",
+ "amended_from"
],
"fields": [
{
@@ -75,7 +80,8 @@
},
{
"fieldname": "section_break_8",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Purpose & Amount"
},
{
"fieldname": "purpose",
@@ -114,7 +120,8 @@
},
{
"fieldname": "section_break_7",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Accounting"
},
{
"fieldname": "status",
@@ -125,10 +132,12 @@
"read_only": 1
},
{
+ "fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
+ "read_only": 1,
"reqd": 1
},
{
@@ -145,6 +154,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "company.default_employee_advance_account",
"fieldname": "advance_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -196,11 +206,35 @@
"precision": "9",
"print_hide": 1,
"reqd": 1
+ },
+ {
+ "fieldname": "column_break_nhlv",
+ "fieldtype": "Column Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "more_info_section",
+ "fieldtype": "Section Break",
+ "label": "More Info"
+ },
+ {
+ "fieldname": "column_break_kimx",
+ "fieldtype": "Column Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "currency_section",
+ "fieldtype": "Section Break",
+ "label": "Currency "
+ },
+ {
+ "fieldname": "column_break_crso",
+ "fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2023-03-21 18:24:06.416439",
+ "modified": "2023-08-19 23:40:06.298367",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
diff --git a/hrms/hr/doctype/expense_claim/expense_claim.js b/hrms/hr/doctype/expense_claim/expense_claim.js
index 166c7fc1ab..bee1e88515 100644
--- a/hrms/hr/doctype/expense_claim/expense_claim.js
+++ b/hrms/hr/doctype/expense_claim/expense_claim.js
@@ -62,10 +62,6 @@ frappe.ui.form.on('Expense Claim Detail', {
}
});
-cur_frm.add_fetch('employee', 'company', 'company');
-cur_frm.add_fetch('employee','employee_name','employee_name');
-cur_frm.add_fetch('expense_type','description','description');
-
cur_frm.cscript.onload = function(doc) {
if (doc.__islocal) {
cur_frm.set_value("posting_date", frappe.datetime.get_today());
diff --git a/hrms/hr/doctype/expense_claim/expense_claim.json b/hrms/hr/doctype/expense_claim/expense_claim.json
index 45b78bfb54..e97bddc61a 100644
--- a/hrms/hr/doctype/expense_claim/expense_claim.json
+++ b/hrms/hr/doctype/expense_claim/expense_claim.json
@@ -7,19 +7,21 @@
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
+ "expenses_and_advances_tab",
"naming_series",
"employee",
"employee_name",
"department",
+ "company",
"column_break_5",
"expense_approver",
"approval_status",
- "delivery_trip",
- "is_paid",
"expense_details",
"expenses",
- "sb1",
+ "taxes_and_charges_sb",
"taxes",
+ "advance_payments_sb",
+ "advances",
"transactions_section",
"total_sanctioned_amount",
"total_taxes_and_charges",
@@ -28,29 +30,28 @@
"grand_total",
"total_claimed_amount",
"total_amount_reimbursed",
- "section_break_16",
- "posting_date",
- "vehicle_log",
- "task",
- "cb1",
- "remark",
- "title",
- "email_id",
+ "accounting_details_tab",
"accounting_details",
- "company",
+ "posting_date",
+ "is_paid",
"mode_of_payment",
- "clearance_date",
- "column_break_24",
"payable_account",
+ "column_break_24",
+ "clearance_date",
+ "remark",
"accounting_dimensions_section",
"project",
"dimension_col_break",
"cost_center",
+ "more_info_tab",
"more_details",
"status",
+ "task",
"amended_from",
- "advance_payments",
- "advances"
+ "column_break_xdzn",
+ "delivery_trip",
+ "vehicle_log",
+ "dashboard_tab"
],
"fields": [
{
@@ -157,11 +158,6 @@
"options": "Expense Claim Detail",
"reqd": 1
},
- {
- "fieldname": "sb1",
- "fieldtype": "Section Break",
- "options": "Simple"
- },
{
"default": "Today",
"fieldname": "posting_date",
@@ -192,10 +188,6 @@
"options": "Task",
"remember_last_selected_value": 1
},
- {
- "fieldname": "cb1",
- "fieldtype": "Column Break"
- },
{
"fieldname": "total_amount_reimbursed",
"fieldtype": "Currency",
@@ -213,30 +205,13 @@
"oldfieldname": "remark",
"oldfieldtype": "Small Text"
},
- {
- "allow_on_submit": 1,
- "default": "{employee_name}",
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Title",
- "no_copy": 1
- },
- {
- "fieldname": "email_id",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Employees Email Id",
- "oldfieldname": "email_id",
- "oldfieldtype": "Data",
- "print_hide": 1
- },
{
"fieldname": "accounting_details",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
+ "fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"in_standard_filter": 1,
@@ -244,7 +219,7 @@
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
- "remember_last_selected_value": 1,
+ "read_only": 1,
"reqd": 1
},
{
@@ -277,10 +252,8 @@
"options": "Cost Center"
},
{
- "collapsible": 1,
"fieldname": "more_details",
- "fieldtype": "Section Break",
- "label": "More Details"
+ "fieldtype": "Section Break"
},
{
"default": "Draft",
@@ -308,7 +281,8 @@
"width": "160px"
},
{
- "fieldname": "advance_payments",
+ "collapsible": 1,
+ "fieldname": "advance_payments_sb",
"fieldtype": "Section Break",
"label": "Advance Payments"
},
@@ -340,13 +314,10 @@
"label": "Expense Taxes and Charges",
"options": "Expense Taxes and Charges"
},
- {
- "fieldname": "section_break_16",
- "fieldtype": "Section Break"
- },
{
"fieldname": "transactions_section",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Totals"
},
{
"fieldname": "grand_total",
@@ -373,13 +344,45 @@
"fieldtype": "Link",
"label": "Delivery Trip",
"options": "Delivery Trip"
+ },
+ {
+ "fieldname": "column_break_xdzn",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "accounting_details_tab",
+ "fieldtype": "Tab Break",
+ "label": "Accounting"
+ },
+ {
+ "fieldname": "more_info_tab",
+ "fieldtype": "Tab Break",
+ "label": "More Info"
+ },
+ {
+ "fieldname": "dashboard_tab",
+ "fieldtype": "Tab Break",
+ "label": "Dashboard",
+ "show_dashboard": 1
+ },
+ {
+ "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": "2021-11-22 16:26:57.787838",
+ "modified": "2023-08-15 15:31:49.277947",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
@@ -445,6 +448,32 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "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": "title"
+ "title_field": "employee_name"
}
\ No newline at end of file
diff --git a/hrms/hr/doctype/expense_claim/expense_claim.py b/hrms/hr/doctype/expense_claim/expense_claim.py
index c776aca256..f3a20a6654 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
(
@@ -82,6 +82,22 @@ def set_status(self, update=False):
def on_update(self):
share_doc_with_approver(self, self.expense_approver)
+ self.publish_update()
+
+ def after_delete(self):
+ self.publish_update()
+
+ def publish_update(self):
+ if frappe.session.user in [self.expense_approver, self.employee]:
+ frappe.publish_realtime(
+ event="hrms:update_expense_claims",
+ message={
+ "approver": self.expense_approver,
+ "employee": self.employee,
+ },
+ user=frappe.session.user,
+ after_commit=True,
+ )
def set_payable_account(self):
if not self.payable_account and not self.is_paid:
@@ -113,6 +129,7 @@ def on_cancel(self):
update_reimbursed_amount(self)
self.update_claimed_amount_in_employee_advance()
+ self.publish_update()
def update_claimed_amount_in_employee_advance(self):
for d in self.get("advances"):
@@ -280,7 +297,7 @@ def calculate_taxes(self):
self.total_taxes_and_charges = 0
for tax in self.taxes:
if tax.rate:
- tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate / 100)
+ tax.tax_amount = flt(self.total_sanctioned_amount) * flt(flt(tax.rate) / 100)
tax.total = flt(tax.tax_amount) + flt(self.total_sanctioned_amount)
self.total_taxes_and_charges += flt(tax.tax_amount)
@@ -463,6 +480,7 @@ def get_advances(employee, advance_id=None):
query = frappe.qb.from_(advance).select(
advance.name,
+ advance.purpose,
advance.posting_date,
advance.paid_amount,
advance.claimed_amount,
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"];
- }
- }
-};
diff --git a/hrms/hr/doctype/expense_claim_detail/expense_claim_detail.json b/hrms/hr/doctype/expense_claim_detail/expense_claim_detail.json
index d224ad0d8d..052faa2c0c 100644
--- a/hrms/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/hrms/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -9,9 +9,9 @@
"column_break_2",
"expense_type",
"default_account",
- "section_break_4",
+ "description_sb",
"description",
- "section_break_6",
+ "amounts_sb",
"amount",
"column_break_8",
"sanctioned_amount",
@@ -22,6 +22,7 @@
],
"fields": [
{
+ "default": "Today",
"fieldname": "expense_date",
"fieldtype": "Date",
"in_list_view": 1,
@@ -57,10 +58,7 @@
"read_only": 1
},
{
- "fieldname": "section_break_4",
- "fieldtype": "Section Break"
- },
- {
+ "fetch_from": "expense_type.description",
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
@@ -70,10 +68,6 @@
"print_width": "300px",
"width": "300px"
},
- {
- "fieldname": "section_break_6",
- "fieldtype": "Section Break"
- },
{
"fieldname": "amount",
"fieldtype": "Currency",
@@ -121,12 +115,20 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
+ },
+ {
+ "fieldname": "description_sb",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "amounts_sb",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-06-24 00:56:08.900677",
+ "modified": "2023-08-07 14:07:19.298844",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
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",
diff --git a/hrms/hr/doctype/leave_application/leave_application.js b/hrms/hr/doctype/leave_application/leave_application.js
index 5f16ee746b..7ba7a88321 100755
--- a/hrms/hr/doctype/leave_application/leave_application.js
+++ b/hrms/hr/doctype/leave_application/leave_application.js
@@ -1,9 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.add_fetch('employee', 'employee_name', 'employee_name');
-cur_frm.add_fetch('employee', 'company', 'company');
-
frappe.ui.form.on("Leave Application", {
setup: function(frm) {
frm.set_query("leave_approver", function() {
@@ -18,6 +15,7 @@ frappe.ui.form.on("Leave Application", {
frm.set_query("employee", erpnext.queries.employee);
},
+
onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
@@ -41,17 +39,18 @@ frappe.ui.form.on("Leave Application", {
},
validate: function(frm) {
- if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1) {
+ if (frm.doc.from_date === frm.doc.to_date && cint(frm.doc.half_day)) {
frm.doc.half_day_date = frm.doc.from_date;
- } else if (frm.doc.half_day == 0) {
+ } else if (frm.doc.half_day === 0) {
frm.doc.half_day_date = "";
}
- frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
+ frm.toggle_reqd("half_day_date", cint(frm.doc.half_day));
},
make_dashboard: function(frm) {
- var leave_details;
+ let leave_details;
let lwps;
+
if (frm.doc.employee) {
frappe.call({
method: "hrms.hr.doctype.leave_application.leave_application.get_leave_details",
@@ -61,32 +60,34 @@ frappe.ui.form.on("Leave Application", {
date: frm.doc.from_date || frm.doc.posting_date
},
callback: function(r) {
- if (!r.exc && r.message['leave_allocation']) {
- leave_details = r.message['leave_allocation'];
+ if (!r.exc && r.message["leave_allocation"]) {
+ leave_details = r.message["leave_allocation"];
}
- if (!r.exc && r.message['leave_approver']) {
- frm.set_value('leave_approver', r.message['leave_approver']);
+ if (!r.exc && r.message["leave_approver"]) {
+ frm.set_value("leave_approver", r.message["leave_approver"]);
}
lwps = r.message["lwps"];
}
});
+
$("div").remove(".form-dashboard-section.custom");
+
frm.dashboard.add_section(
- frappe.render_template('leave_application_dashboard', {
+ frappe.render_template("leave_application_dashboard", {
data: leave_details
}),
__("Allocated Leaves")
);
frm.dashboard.show();
- let allowed_leave_types = Object.keys(leave_details);
- // lwps should be allowed, lwps don't have any allocation
+ let allowed_leave_types = Object.keys(leave_details);
+ // lwps should be allowed for selection as they don't have any allocation
allowed_leave_types = allowed_leave_types.concat(lwps);
- frm.set_query('leave_type', function() {
+ frm.set_query("leave_type", function() {
return {
filters: [
- ['leave_type_name', 'in', allowed_leave_types]
+ ["leave_type_name", "in", allowed_leave_types]
]
};
});
@@ -97,15 +98,16 @@ frappe.ui.form.on("Leave Application", {
if (frm.is_new()) {
frm.trigger("calculate_total_days");
}
- cur_frm.set_intro("");
+
+ frm.set_intro("");
if (frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) {
frm.set_intro(__("Fill the form and save it"));
}
if (!frm.doc.employee && frappe.defaults.get_user_permissions()) {
const perm = frappe.defaults.get_user_permissions();
- if (perm && perm['Employee']) {
- frm.set_value('employee', perm['Employee'].map(perm_doc => perm_doc.doc)[0]);
+ if (perm && perm["Employee"]) {
+ frm.set_value("employee", perm["Employee"].map(perm_doc => perm_doc.doc)[0]);
}
}
},
@@ -156,8 +158,8 @@ frappe.ui.form.on("Leave Application", {
},
half_day_datepicker: function(frm) {
- frm.set_value('half_day_date', '');
- var half_day_datepicker = frm.fields_dict.half_day_date.datepicker;
+ frm.set_value("half_day_date", "");
+ let half_day_datepicker = frm.fields_dict.half_day_date.datepicker;
half_day_datepicker.update({
minDate: frappe.datetime.str_to_obj(frm.doc.from_date),
maxDate: frappe.datetime.str_to_obj(frm.doc.to_date)
@@ -177,9 +179,9 @@ frappe.ui.form.on("Leave Application", {
},
callback: function (r) {
if (!r.exc && r.message) {
- frm.set_value('leave_balance', r.message);
+ frm.set_value("leave_balance", r.message);
} else {
- frm.set_value('leave_balance', "0");
+ frm.set_value("leave_balance", "0");
}
}
});
@@ -188,18 +190,17 @@ frappe.ui.form.on("Leave Application", {
calculate_total_days: function(frm) {
if (frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) {
-
- var from_date = Date.parse(frm.doc.from_date);
- var to_date = Date.parse(frm.doc.to_date);
+ let from_date = Date.parse(frm.doc.from_date);
+ let to_date = Date.parse(frm.doc.to_date);
if (to_date < from_date) {
frappe.msgprint(__("To Date cannot be less than From Date"));
- frm.set_value('to_date', '');
+ frm.set_value("to_date", "");
return;
}
// server call is done to include holidays in leave days calculations
return frappe.call({
- method: 'hrms.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
+ method: "hrms.hr.doctype.leave_application.leave_application.get_number_of_leave_days",
args: {
"employee": frm.doc.employee,
"leave_type": frm.doc.leave_type,
@@ -210,7 +211,7 @@ frappe.ui.form.on("Leave Application", {
},
callback: function(r) {
if (r && r.message) {
- frm.set_value('total_leave_days', r.message);
+ frm.set_value("total_leave_days", r.message);
frm.trigger("get_leave_balance");
}
}
@@ -220,15 +221,14 @@ frappe.ui.form.on("Leave Application", {
set_leave_approver: function(frm) {
if (frm.doc.employee) {
- // server call is done to include holidays in leave days calculations
return frappe.call({
- method: 'hrms.hr.doctype.leave_application.leave_application.get_leave_approver',
+ method: "hrms.hr.doctype.leave_application.leave_application.get_leave_approver",
args: {
"employee": frm.doc.employee,
},
callback: function(r) {
if (r && r.message) {
- frm.set_value('leave_approver', r.message);
+ frm.set_value("leave_approver", r.message);
}
}
});
diff --git a/hrms/hr/doctype/leave_application/leave_application.json b/hrms/hr/doctype/leave_application/leave_application.json
index 7f50ace766..58480a5892 100644
--- a/hrms/hr/doctype/leave_application/leave_application.json
+++ b/hrms/hr/doctype/leave_application/leave_application.json
@@ -13,8 +13,8 @@
"employee_name",
"column_break_4",
"leave_type",
+ "company",
"department",
- "leave_balance",
"section_break_5",
"from_date",
"to_date",
@@ -23,18 +23,18 @@
"total_leave_days",
"column_break1",
"description",
+ "leave_balance",
"section_break_7",
"leave_approver",
"leave_approver_name",
+ "follow_via_email",
"column_break_18",
+ "posting_date",
"status",
+ "sb_other_details",
"salary_slip",
- "sb10",
- "posting_date",
- "follow_via_email",
"color",
"column_break_17",
- "company",
"letter_head",
"amended_from"
],
@@ -60,6 +60,7 @@
"search_index": 1
},
{
+ "fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"in_global_search": 1,
@@ -97,7 +98,8 @@
},
{
"fieldname": "section_break_5",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Dates & Reason"
},
{
"fieldname": "from_date",
@@ -148,7 +150,8 @@
},
{
"fieldname": "section_break_7",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Approval"
},
{
"fieldname": "leave_approver",
@@ -177,10 +180,6 @@
"permlevel": 1,
"reqd": 1
},
- {
- "fieldname": "sb10",
- "fieldtype": "Section Break"
- },
{
"default": "Today",
"fieldname": "posting_date",
@@ -243,6 +242,12 @@
"options": "Leave Application",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "sb_other_details",
+ "fieldtype": "Section Break",
+ "label": "Other Details"
}
],
"icon": "fa fa-calendar",
@@ -250,10 +255,11 @@
"is_submittable": 1,
"links": [],
"max_attachments": 3,
- "modified": "2020-05-18 13:00:41.577327",
+ "modified": "2023-08-01 22:45:57.851854",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Application",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -333,6 +339,7 @@
"search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "employee",
"title_field": "employee_name"
}
\ No newline at end of file
diff --git a/hrms/hr/doctype/leave_application/leave_application.py b/hrms/hr/doctype/leave_application/leave_application.py
index 48faa609e1..d5df87e9e3 100755
--- a/hrms/hr/doctype/leave_application/leave_application.py
+++ b/hrms/hr/doctype/leave_application/leave_application.py
@@ -123,15 +123,16 @@ def after_delete(self):
self.publish_update()
def publish_update(self):
- frappe.publish_realtime(
- event="hrms:update_leaves",
- message={
- "approver": self.leave_approver,
- "employee": self.employee,
- },
- user=frappe.session.user,
- after_commit=True,
- )
+ if frappe.session.user in [self.employee, self.leave_approver]:
+ frappe.publish_realtime(
+ event="hrms:update_leaves",
+ message={
+ "approver": self.leave_approver,
+ "employee": self.employee,
+ },
+ user=frappe.session.user,
+ after_commit=True,
+ )
def validate_applicable_after(self):
if self.leave_type: