Skip to content

Commit

Permalink
feat: WIP creating invoices
Browse files Browse the repository at this point in the history
  • Loading branch information
JustSamuel committed Oct 14, 2024
1 parent 07094aa commit 9ca3afa
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 47 deletions.
12 changes: 9 additions & 3 deletions apps/dashboard/src/components/FindUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { onMounted, type PropType, ref, watch } from "vue";
import type { Ref } from "vue";
import apiService from "@/services/ApiService";
import { debounce } from "lodash";
import type { BaseUserResponse, UserResponse } from "@sudosos/sudosos-client";
import { type BaseUserResponse, GetAllUsersTypeEnum, type UserResponse } from "@sudosos/sudosos-client";
const lastQuery = ref("");
const selectedUser = ref(null);
Expand All @@ -31,7 +31,7 @@ const loading = ref(false);
const users: Ref<(BaseUserResponse & { fullName: string })[]> = ref([]);
const emits = defineEmits(['update:value']);
defineProps({
const props = defineProps({
value: {
type: Object as PropType<UserResponse>,
},
Expand All @@ -40,6 +40,11 @@ defineProps({
required: false,
default: ''
},
type: {
type: String as PropType<GetAllUsersTypeEnum>,
required: false,
default: undefined
}
});
const transformUsers = (userData: BaseUserResponse[]) => {
Expand All @@ -50,8 +55,9 @@ const transformUsers = (userData: BaseUserResponse[]) => {
};
const debouncedSearch = debounce((e: any) => {
console.error('type', props.type);
loading.value = true;
apiService.user.getAllUsers(50, 0, e.value).then((res) => {
apiService.user.getAllUsers(50, 0, e.value, undefined, undefined, undefined, props.type).then((res) => {
users.value = transformUsers(res.data.records); // Transform users
}).finally(() => {
loading.value = false;
Expand Down
10 changes: 8 additions & 2 deletions apps/dashboard/src/components/InputUserSpan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<span class="my-0">{{ label }}</span>
<FindUser :placeholder="placeholder"
v-model="internalValue"
:disabled="disabled"/>
:disabled="disabled"
:type="type"/>
</span>
<div class="flex justify-content-end">
<ErrorSpan :error="errors"/>
Expand All @@ -17,7 +18,7 @@
import ErrorSpan from "@/components/ErrorSpan.vue";
import { onMounted, type PropType, ref, watch } from "vue";
import FindUser from "@/components/FindUser.vue";
import type { BaseUserResponse } from "@sudosos/sudosos-client";
import { type BaseUserResponse, GetAllUsersTypeEnum } from "@sudosos/sudosos-client";
const emit = defineEmits(['update:value']);
Expand Down Expand Up @@ -48,6 +49,11 @@ const props = defineProps({
required: false,
default: false
},
type: {
type: String as PropType<GetAllUsersTypeEnum>,
required: false,
default: undefined
}
});
const internalValue = ref();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
{{ formatPrice((mutation.data as FinancialMutation).amount) }}
</div>

<!-- Fines get green -->
<!-- Fines get red -->
<div v-else-if="isFine(mutation.data.type)" style="color: #d40000"
class="font-bold">
{{ formatPrice((mutation.data as FinancialMutation).amount, true) }}
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/src/locales/en/modules/financial.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"missingPdf": "PDF is missing or generation failed",
"deleted": "Invoice has been deleted.",
"dirty": "Invoice amount does not match transfer amount.",
"total": "The current total on the invoice is {total}"
"total": "The current total on the invoice is {total}",
"create": "Create invoice"
},
"payout": {
"title": "Payout overview",
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/src/locales/nl/modules/financial.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"missingPdf": "PDF ontbreekt of genereren is mislukt",
"deleted": "Factuur is verwijderd.",
"dirty": "De factuurbedrag komt niet overeen met de overboeking.",
"total": "Het huidige totaal op de factuur is {total}"
"total": "Het huidige totaal op de factuur is {total}",
"create": "Factuur aanmaken"
},
"payout": {
"title": "Uitbetalingsoverzicht",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<FormCard :header="t('modules.financial.invoice.transfer')" @cancel="form.context.resetForm" @save="formSubmit" :create="true">
<div class="flex flex-column justify-content-between gap-2">
<InvoiceTransactionsForm :form="form"/>
</div>
</FormCard>
</template>

<script setup lang="ts">
import FormCard from "@/components/FormCard.vue";
import InvoiceTransactionsForm from "@/modules/financial/components/invoice/forms/InvoiceTransactionsForm.vue";
import { createInvoiceObject } from "@/utils/validation-schema";
import { type Form } from "@/utils/formUtils";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
const { t } = useI18n();
const props = defineProps({
form: {
type: Object as PropType<Form<yup.InferType<typeof createInvoiceObject>>>,
required: true,
},
});
const formSubmit = () => {
props.form.submit();
};
</script>

<style scoped lang="scss">
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<template>
<div class="flex flex-column gap-3">
<InputUserSpan :label="t('modules.financial.invoice.for')"
:type="GetAllUsersTypeEnum.Invoice"
:value="selectedUser"
@update:value="selectedUser = $event"
/>
<Calendar v-model="dates" inline showButtonBar selectionMode="range" :manualInput="true" :pt='{
day: (options) => {
console.error(options.context.date);
const dateString = `${options.context.date.year}-${options.context.date.month}-${options.context.date.day}`;
console.error(dateString, transactionsPerDay[dateString]);
if (transactionsPerDay[dateString]) {
return {
class: "transaction-day-selected",
}
}
}
}' @month-change="monthChange($event)"/>
<DataTable :value="transactions" class="w-full">
<Column field="createdAt" :header="t('components.mutations.when')">
<template #body v-if="isLoading">
<Skeleton class="w-3 my-1 h-1rem surface-300"/>
</template>
<template #body="transaction" v-else>
{{ dateString(transaction.data) }}
</template>
</Column>
<Column field="pointOfSale" :header="t('components.mutations.pos')">
<template #body v-if="isLoading">
<Skeleton class="w-3 my-1 h-1rem surface-300"/>
</template>
<template #body="transaction" v-else>
{{ transaction.data.pointOfSale.name }}
</template>
</Column>
<Column field="totalPriceInclVat" :header="t('components.mutations.amount')">
<template #body v-if="isLoading">
<Skeleton class="w-3 my-1 h-1rem surface-300"/>
</template>
<template #body="transaction" v-else>
<div>
{{ formatPrice(transaction.data.totalPriceInclVat, true) }}
</div>
</template>
</Column>
</DataTable>
</div>
</template>

<script setup lang="ts">
import { useI18n } from "vue-i18n";
import type { createInvoiceObject } from "@/utils/validation-schema";
import { computed, onBeforeMount, type PropType, type Ref, ref, watch } from "vue";
import * as yup from "yup";
import type { Form } from "@/utils/formUtils";
import InputUserSpan from "@/components/InputUserSpan.vue";
import { type BaseTransactionResponse, GetAllUsersTypeEnum } from "@sudosos/sudosos-client";
import apiService from "@/services/ApiService";
import Column from "primevue/column";
import { formatPrice } from "sudosos-dashboard/src/utils/formatterUtils";
const { t } = useI18n();
const props = defineProps({
form: {
type: Object as PropType<Form<yup.InferType<typeof createInvoiceObject>>>,
required: true,
},
});
const dates = ref<(Date|null)[]>([]);
const selectedUser = ref();
const transactions: Ref<BaseTransactionResponse[]>= ref();
const isLoading: Ref<boolean> = ref(false);
const getTransactions = () => {
isLoading.value = true;
const fromDate = new Date(dates.value[0]);
const toDate = new Date(dates.value[1]);
fromDate.setHours(0, 0, 0, 0);
toDate.setHours(23, 59, 59, 999);
apiService.invoices.getEligibleTransactions(selectedUser.value.id, fromDate.toISOString(), toDate.toISOString()).then((res) => {
transactions.value = res.data;
console.error(res.data);
}).finally(() => {
isLoading.value = false;
});
};
const isShowing = computed(() => {
return showTransactions();
});
const showTransactions = () => {
if (!dates.value || dates.value.length !== 2) return false;
return selectedUser.value !== undefined && dates.value[0] !== null && dates.value[1] !== null;
};
watch(selectedUser, () => {
if (selectedUser.value) {
const now = new Date();
getTransactionsHeatMap({ month: now.getMonth(), year: now.getFullYear() });
}
if (showTransactions()) getTransactions();
else transactions.value = [];
});
watch(dates, () => {
if (showTransactions()) getTransactions();
else transactions.value = [];
});
const dateString = (transaction: BaseTransactionResponse) => {
return new Date(transaction.createdAt!!).toLocaleString('nl-NL', {
dateStyle: 'short',
timeStyle: 'short'
});
};
// Mapping between the date and the transactions
const transactionsPerDay: { [key: string]: boolean } = {};
const loadingHeatmap = ref(false);
const getTransactionsHeatMap = (event: { month: number, year: number }) => {
loadingHeatmap.value = true;
const fromDate = new Date(event.year, event.month, 1);
const toDate = new Date(event.year, event.month + 1, 0);
fromDate.setHours(0, 0, 0, 0);
toDate.setHours(0, 0, 0, 0);
apiService.invoices.getEligibleTransactions(selectedUser.value.id,
fromDate.toISOString(), toDate.toISOString()).then((res) => {
res.data.forEach((transaction: BaseTransactionResponse) => {
const date = new Date(transaction.createdAt);
const dateKey = `${date.getFullYear()}-${date.getUTCMonth()}-${date.getDate()}`;
console.error(dateKey);
transactionsPerDay[dateKey] = true;
});
}).finally(() => {
loadingHeatmap.value = false;
});
};
const monthChange = (event: { month: number, year: number }) => {
getTransactionsHeatMap(event);
};
</script>

<style lang="scss">
.p-datepicker table td.transaction-day-selected > span {
background-color: rgba(11, 114, 0, 0.89) !important;
color: white !important;
font-weight: bold;
}
</style>
91 changes: 56 additions & 35 deletions apps/dashboard/src/modules/financial/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PayoutsView from "@/modules/financial/views/payouts/PayoutsView.vue";
import { UserRole } from "@/utils/rbacUtils";
import InvoiceOverview from "@/modules/financial/views/invoice/InvoiceOverview.vue";
import InvoiceInfoView from "@/modules/financial/views/invoice/InvoiceInfoView.vue";
import InvoiceCreateView from "@/modules/financial/views/invoice/InvoiceCreateView.vue";

export function financialRoutes(): RouteRecordRaw[] {
return [
Expand All @@ -14,42 +15,62 @@ export function financialRoutes(): RouteRecordRaw[] {
meta: { requiresAuth: true },
children: [
{
path: '/fine',
component: FineView,
name: 'fine',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice',
component: InvoiceOverview,
name: 'invoice',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice/:id/info',
name: 'invoiceInfo',
component: InvoiceInfoView,
props: true,
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/payouts',
component: PayoutsView,
name: 'payouts',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
path: '/fine',
component: FineView,
name: 'fine',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice',
component: InvoiceOverview,
name: 'invoice',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice/create',
component: InvoiceCreateView,
name: 'invoiceCreate',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice/:id',
redirect: to => {
const { id } = to.params;
return `/invoice/${id}/info`;
},
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/invoice/:id/info',
name: 'invoiceInfo',
component: InvoiceInfoView,
props: true,
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
},
{
path: '/payouts',
component: PayoutsView,
name: 'payouts',
meta: {
requiresAuth: true,
rolesAllowed: [UserRole.BAC_PM]
}
}
}
]
}
];
Expand Down
Loading

0 comments on commit 9ca3afa

Please sign in to comment.