diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index ae95dae478..dfe79f7d1b 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -18,4 +18,7 @@ b55d6e27af6bd274dfa47e66a3012ddec68ce798 # bulk formatting PWA frontend code -f37f15b2b5329e3b0b35891e1c4fd82f48562c6d \ No newline at end of file +f37f15b2b5329e3b0b35891e1c4fd82f48562c6d + +# bulk formatting PWA frontend code +920daa1a3ddccaefaf7b9348f850831d6e0a0e6b \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 8770c499b0..30fa865ee7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -11,6 +11,8 @@ + + +
+ + + + + +
+ +
+
+
+ + + + diff --git a/frontend/src/components/EmployeeAdvanceItem.vue b/frontend/src/components/EmployeeAdvanceItem.vue new file mode 100644 index 0000000000..ff50065183 --- /dev/null +++ b/frontend/src/components/EmployeeAdvanceItem.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/components/EmptyState.vue b/frontend/src/components/EmptyState.vue new file mode 100644 index 0000000000..4fc11f8cb6 --- /dev/null +++ b/frontend/src/components/EmptyState.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontend/src/components/ExpenseAdvancesTable.vue b/frontend/src/components/ExpenseAdvancesTable.vue new file mode 100644 index 0000000000..85ae7c1b0d --- /dev/null +++ b/frontend/src/components/ExpenseAdvancesTable.vue @@ -0,0 +1,83 @@ + + + diff --git a/frontend/src/components/ExpenseClaimItem.vue b/frontend/src/components/ExpenseClaimItem.vue new file mode 100644 index 0000000000..6699d578ad --- /dev/null +++ b/frontend/src/components/ExpenseClaimItem.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/components/ExpenseClaimSummary.vue b/frontend/src/components/ExpenseClaimSummary.vue new file mode 100644 index 0000000000..9ece555b25 --- /dev/null +++ b/frontend/src/components/ExpenseClaimSummary.vue @@ -0,0 +1,83 @@ + + + diff --git a/frontend/src/components/ExpenseItems.vue b/frontend/src/components/ExpenseItems.vue new file mode 100644 index 0000000000..94ea8f0862 --- /dev/null +++ b/frontend/src/components/ExpenseItems.vue @@ -0,0 +1,53 @@ + + + diff --git a/frontend/src/components/ExpenseTaxesTable.vue b/frontend/src/components/ExpenseTaxesTable.vue new file mode 100644 index 0000000000..6ad663f983 --- /dev/null +++ b/frontend/src/components/ExpenseTaxesTable.vue @@ -0,0 +1,266 @@ + + + diff --git a/frontend/src/components/ExpensesTable.vue b/frontend/src/components/ExpensesTable.vue new file mode 100644 index 0000000000..a234be04cd --- /dev/null +++ b/frontend/src/components/ExpensesTable.vue @@ -0,0 +1,232 @@ + + + diff --git a/frontend/src/components/FileUploaderView.vue b/frontend/src/components/FileUploaderView.vue new file mode 100644 index 0000000000..461353947a --- /dev/null +++ b/frontend/src/components/FileUploaderView.vue @@ -0,0 +1,92 @@ + + + diff --git a/frontend/src/components/FormField.vue b/frontend/src/components/FormField.vue new file mode 100644 index 0000000000..bd7f7eeeb3 --- /dev/null +++ b/frontend/src/components/FormField.vue @@ -0,0 +1,239 @@ + + + diff --git a/frontend/src/components/FormView.vue b/frontend/src/components/FormView.vue new file mode 100644 index 0000000000..4454e97d42 --- /dev/null +++ b/frontend/src/components/FormView.vue @@ -0,0 +1,595 @@ + + + diff --git a/frontend/src/components/Holidays.vue b/frontend/src/components/Holidays.vue index 6ecbdd1f61..fe6bbffdf5 100644 --- a/frontend/src/components/Holidays.vue +++ b/frontend/src/components/Holidays.vue @@ -3,6 +3,7 @@
Upcoming Holidays
@@ -10,7 +11,10 @@
-
+
+ +
Leave Balance
-
- View Leave History +
+ +
+ View Leave History +
+
-
+
+ +
diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue new file mode 100644 index 0000000000..abfb3eade1 --- /dev/null +++ b/frontend/src/components/ListView.vue @@ -0,0 +1,287 @@ + + + diff --git a/frontend/src/components/MenuLinks.vue b/frontend/src/components/MenuLinks.vue index 12cbc31a47..224e1105df 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: "file", + 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/components/QuickLinks.vue b/frontend/src/components/QuickLinks.vue index b83665ca0f..be8961b804 100644 --- a/frontend/src/components/QuickLinks.vue +++ b/frontend/src/components/QuickLinks.vue @@ -2,17 +2,18 @@
{{ title }}
-
{{ link.title }}
-
+
diff --git a/frontend/src/components/RequestActionSheet.vue b/frontend/src/components/RequestActionSheet.vue index 2dbeccf275..907bfcf03a 100644 --- a/frontend/src/components/RequestActionSheet.vue +++ b/frontend/src/components/RequestActionSheet.vue @@ -1,104 +1,231 @@ 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 @@ 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 @@ - - - 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 @@ + + + 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 @@ + + + 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: