Skip to content

Commit

Permalink
Merge pull request #822 from ruchamahabal/payslips
Browse files Browse the repository at this point in the history
  • Loading branch information
ruchamahabal authored Aug 27, 2023
2 parents 819abd8 + ed009af commit e7e4317
Show file tree
Hide file tree
Showing 20 changed files with 640 additions and 46 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/ExpenseTaxesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<h2 class="text-lg font-semibold text-gray-800">Taxes & Charges</h2>
<div class="flex flex-row gap-3 items-center">
<span class="text-lg font-semibold text-gray-800">
{{ `${currency} ${expenseClaim.total_taxes_and_charges || 0}` }}
{{ expenseClaim.total_taxes_and_charges || 0 }}
</span>
<Button
id="add-taxes-modal"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ExpensesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<h2 class="text-lg font-semibold text-gray-800">Expenses</h2>
<div class="flex flex-row gap-3 items-center">
<span class="text-lg font-semibold text-gray-800">
{{ `${currency} ${expenseClaim.total_claimed_amount || 0}` }}
{{ expenseClaim.total_claimed_amount || 0 }}
</span>
<Button
id="add-expense-modal"
Expand Down
36 changes: 17 additions & 19 deletions frontend/src/components/FormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
:disabled="isReadOnly"
/>

<!-- Read only currency field -->
<Input
v-else-if="props.fieldtype === 'Currency' && isReadOnly"
type="text"
:value="modelValue"
@input="(v) => emit('update:modelValue', v)"
@change="(v) => emit('change', v)"
v-bind="$attrs"
:disabled="isReadOnly"
/>

<!-- Float/Int field -->
<Input
v-else-if="isNumberType"
Expand Down Expand Up @@ -140,37 +151,24 @@ const props = defineProps({
const emit = defineEmits(["change", "update:modelValue"])
const dayjs = inject("$dayjs")
const SUPPORTED_FIELD_TYPES = [
"Link",
"Select",
"Small Text",
"Text",
"Long Text",
"Text Editor",
"Check",
"Data",
"Float",
"Int",
"Section Break",
"Date",
"Time",
"Datetime",
"Currency",
]
let linkFieldList = ref([])
let date = ref(null)
const showField = computed(() => {
if (props.readOnly && !props.modelValue) return false
if (props.readOnly && !isLayoutField.value && !props.modelValue) return false
return SUPPORTED_FIELD_TYPES.includes(props.fieldtype) && !props.hidden
return props.fieldtype !== "Table" && !props.hidden
})
const isNumberType = computed(() => {
return ["Int", "Float", "Currency"].includes(props.fieldtype)
})
const isLayoutField = computed(() => {
return ["Section Break", "Column Break"].includes(props.fieldtype)
})
const isReadOnly = computed(() => {
return Boolean(props.readOnly)
})
Expand Down
46 changes: 42 additions & 4 deletions frontend/src/components/FormView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
class="whitespace-nowrap text-[8px]"
/>
<Badge
v-if="formModel.status"
:label="formModel.status"
v-if="formModel?.status"
:label="formModel?.status"
:color="statusColor"
class="whitespace-nowrap text-[8px]"
/>
Expand Down Expand Up @@ -58,8 +58,8 @@
<div
class="px-4 sticky top-14 z-[100] bg-white text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700"
>
<ul class="flex flex-wrap -mb-px">
<li class="mr-2" v-for="tab in tabs">
<ul class="flex -mb-px overflow-auto hide-scrollbar">
<li class="mr-2 whitespace-nowrap" v-for="tab in tabs">
<button
@click="activeTab = tab.name"
class="inline-block p-4 border-b-2 border-transparent rounded-t-lg"
Expand Down Expand Up @@ -171,6 +171,13 @@
{{ formButton }}
</Button>
</div>

<div
v-else
class="px-4 pt-4 mt-2 sm:w-96 bg-white sticky bottom-0 w-full drop-shadow-xl z-40 border-t rounded-t-xl pb-10"
>
<slot name="formButton"></slot>
</div>
</div>
</div>

Expand Down Expand Up @@ -255,6 +262,8 @@ import FormField from "@/components/FormField.vue"
import FileUploaderView from "@/components/FileUploaderView.vue"
import { FileAttachment, guessStatusColor } from "@/composables/index"
import { getCompanyCurrency } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"
const props = defineProps({
doctype: {
Expand Down Expand Up @@ -292,6 +301,11 @@ const props = defineProps({
required: false,
default: false,
},
showFormButton: {
type: Boolean,
required: false,
default: true,
},
})
const emit = defineEmits(["validateForm", "update:modelValue"])
const router = useRouter()
Expand Down Expand Up @@ -491,6 +505,8 @@ const saveButtonDisabled = computed(() => {
})
const formButton = computed(() => {
if (!props.showFormButton) return
if (props.id && props.isSubmittable && !isFormDirty.value) {
if (formModel.value.docstatus === 0 && hasPermission("submit")) {
return "Submit"
Expand Down Expand Up @@ -572,6 +588,27 @@ async function setStatusColor() {
}
}
async function setFormattedCurrency() {
const companyCurrency = await getCompanyCurrency(formModel.value.company)
props.fields.forEach((field) => {
if (field.fieldtype !== "Currency") return
if (!(field.readOnly || isFormReadOnly.value)) return
if (field.options === "currency") {
formModel.value[field.fieldname] = formatCurrency(
formModel.value[field.fieldname],
formModel.value.currency
)
} else {
formModel.value[field.fieldname] = formatCurrency(
formModel.value[field.fieldname],
companyCurrency
)
}
})
}
const isFormReadOnly = computed(
() => props.id && formModel.value.docstatus !== 0
)
Expand All @@ -588,6 +625,7 @@ onMounted(async () => {
formModel.value = { ...documentResource.doc }
await docPermissions.fetch({ doctype: props.doctype, docname: props.id })
await attachedFiles.reload()
await setFormattedCurrency()
await setStatusColor()
isFormDirty.value = false
}
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/components/MenuLinks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const menuItems = ref([
current: false,
},
{
icon: "file",
icon: "dollar-sign",
title: "Expense Claims",
route: {
name: "ExpenseClaims",
Expand All @@ -64,6 +64,13 @@ const menuItems = ref([
},
current: false,
},
{
icon: "file",
title: "Salary Slips",
route: {
name: "SalarySlips",
},
},
{
icon: "info",
title: "Payment Information",
Expand Down
65 changes: 65 additions & 0 deletions frontend/src/components/SalaryDetailTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template>
<!-- Header -->
<div class="flex flex-row justify-between items-center">
<h2 class="text-lg font-semibold text-gray-800">{{ type }}</h2>
<span class="text-lg font-semibold text-gray-800">
{{ total }}
</span>
</div>

<!-- Table -->
<div
v-if="items"
class="flex flex-col bg-white mt-5 rounded-lg border overflow-auto"
>
<div
class="flex flex-row p-3.5 items-center justify-between border-b"
v-for="(item, idx) in items"
:key="idx"
>
<div
class="text-lg font-normal whitespace-nowrap overflow-hidden text-ellipsis text-gray-800"
>
{{ item.salary_component }}
</div>
<span class="text-gray-700 font-normal rounded-lg text-lg">
{{ formatCurrency(item.amount, salarySlip.currency) }}
</span>
</div>
</div>
<EmptyState v-else message="No expenses added" />
</template>

<script setup>
import { computed } from "vue"
import EmptyState from "@/components/EmptyState.vue"
import { formatCurrency } from "@/utils/formatters"
const props = defineProps({
salarySlip: {
type: Object,
required: true,
},
type: {
type: String,
required: true,
},
isReadOnly: {
type: Boolean,
default: false,
},
})
const items = computed(() => {
return props.type === "Earnings"
? props.salarySlip.earnings
: props.salarySlip.deductions
})
const total = computed(() => {
return props.type === "Earnings"
? props.salarySlip.gross_pay
: props.salarySlip.total_deduction
})
</script>
55 changes: 55 additions & 0 deletions frontend/src/components/SalarySlipItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<div class="flex flex-col w-full justify-center gap-2.5">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-row items-start gap-3 grow">
<SalaryIcon class="h-4 w-4 mt-0.5 text-gray-500" />
<div class="flex flex-col items-start">
<div class="text-lg font-normal text-gray-800">
{{ title }}
</div>
<div class="text-sm font-normal text-gray-500">
<span>
{{ `Gross Pay: ${formatCurrency(doc.gross_pay, doc.currency)}` }}
</span>
<span class="whitespace-pre"> &middot; </span>
</div>
</div>
</div>
<div class="flex flex-row justify-end items-center gap-2">
<span class="text-gray-700 font-normal rounded-lg text-lg">
{{ formatCurrency(doc.net_pay, doc.currency) }}
</span>
<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
</div>
</div>
</div>
</template>

<script setup>
import { computed, inject } from "vue"
import { FeatherIcon } from "frappe-ui"
import SalaryIcon from "@/components/icons/SalaryIcon.vue"
import { formatCurrency } from "@/utils/formatters"
const dayjs = inject("$dayjs")
const props = defineProps({
doc: {
type: Object,
},
})
const title = computed(() => {
if (dayjs(props.doc.start_date).isSame(props.doc.end_date, "month")) {
// monthly salary
return dayjs(props.doc.start_date).format("MMM YYYY")
} else {
// quarterly, bimonthly, etc
return `${dayjs(props.doc.start_date).format("MMM YYYY")} - ${dayjs(
props.doc.end_date
).format("MMM YYYY")}`
}
})
</script>
19 changes: 19 additions & 0 deletions frontend/src/components/icons/SalaryIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 14 14"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="13" height="9" x=".5" y="2.5" rx="1"></rect>
<circle cx="7" cy="7" r="1.5"></circle>
<path d="M3 5h.5m7 4h.5"></path>
</g>
</svg>
</template>
11 changes: 11 additions & 0 deletions frontend/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,14 @@ input:disabled {
--webkit-text-fill-color: var(--text-color);
opacity: 1; /* required on iOS */
}

/* For Webkit-based browsers (Chrome, Safari and Opera) */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}

/* For IE, Edge and Firefox */
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
2 changes: 2 additions & 0 deletions frontend/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { userResource } from "@/data/user"
import leaveRoutes from "./leaves"
import claimRoutes from "./claims"
import employeeAdvanceRoutes from "./advances"
import salarySlipRoutes from "./salary_slips"

const routes = [
{
Expand All @@ -30,6 +31,7 @@ const routes = [
...leaveRoutes,
...claimRoutes,
...employeeAdvanceRoutes,
...salarySlipRoutes,
]

const router = createRouter({
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/router/salary_slips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const routes = [
{
path: "/salary-slip/dashboard",
name: "SalarySlips",
component: () => import("@/views/salary_slip/Dashboard.vue"),
},
{
path: "/salary-slip/:id",
name: "SalarySlipDetailView",
props: true,
component: () => import("@/views/salary_slip/Detail.vue"),
},
]

export default routes
Loading

0 comments on commit e7e4317

Please sign in to comment.