Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot
Modern ERP Reports Application with microservices architecture Tech Stack: - Backend: FastAPI + python-oracledb (Oracle DB integration) - Frontend: Vue.js 3 + PrimeVue + Vite - Telegram Bot: python-telegram-bot + SQLite - Infrastructure: Shared database pool, JWT authentication, SSH tunnel Features: - FastAPI backend with async Oracle connection pool - Vue.js 3 responsive frontend with PrimeVue components - Telegram bot alternative interface - Microservices architecture with shared components - Complete deployment support (Linux Docker + Windows IIS) - Comprehensive testing (Playwright E2E + pytest) Repository Structure: - reports-app/ - Main application (backend, frontend, telegram-bot) - shared/ - Shared components (database pool, auth, utils) - deployment/ - Deployment scripts (Linux & Windows) - docs/ - Project documentation - security/ - Security scanning and git hooks
This commit is contained in:
376
reports-app/frontend/src/views/BankCashRegisterView.vue
Normal file
376
reports-app/frontend/src/views/BankCashRegisterView.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="register-view">
|
||||
<!-- Header -->
|
||||
<div class="view-header">
|
||||
<h1 class="page-title">
|
||||
<i class="pi pi-wallet"></i>
|
||||
Registrul de Casă și Bancă
|
||||
</h1>
|
||||
<p class="page-subtitle">
|
||||
Vizualizați toate mișcările din conturile de bancă și casă
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<Card class="filters-card">
|
||||
<template #content>
|
||||
<div class="filters-grid">
|
||||
<div class="filter-item">
|
||||
<label>Data început</label>
|
||||
<Calendar v-model="filters.dateFrom" dateFormat="dd/mm/yy" />
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>Data sfârșit</label>
|
||||
<Calendar v-model="filters.dateTo" dateFormat="dd/mm/yy" />
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>Căutare partener</label>
|
||||
<InputText v-model="filters.partnerName" placeholder="Nume partener..." />
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<Button
|
||||
label="Aplică Filtre"
|
||||
icon="pi pi-filter"
|
||||
@click="applyFilters"
|
||||
/>
|
||||
<Button
|
||||
label="Resetează"
|
||||
icon="pi pi-times"
|
||||
class="p-button-secondary"
|
||||
@click="resetFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="summary-stats">
|
||||
<Card class="stat-card">
|
||||
<template #content>
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon green">
|
||||
<i class="pi pi-arrow-down"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h3 class="stat-value">{{ formatCurrency(treasuryStore.totals.total_incasari) }}</h3>
|
||||
<p class="stat-label">Total Încasări</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
<Card class="stat-card">
|
||||
<template #content>
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon red">
|
||||
<i class="pi pi-arrow-up"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h3 class="stat-value">{{ formatCurrency(treasuryStore.totals.total_plati) }}</h3>
|
||||
<p class="stat-label">Total Plăți</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card class="data-card">
|
||||
<template #content>
|
||||
<DataTable
|
||||
:value="treasuryStore.registers"
|
||||
:loading="treasuryStore.isLoading"
|
||||
:paginator="true"
|
||||
:rows="pagination.rows"
|
||||
:total-records="treasuryStore.pagination.totalRecords"
|
||||
:lazy="true"
|
||||
@page="onPage"
|
||||
class="p-datatable-sm"
|
||||
:rowClass="getRowClass"
|
||||
>
|
||||
<Column field="dataact" header="Data">
|
||||
<template #body="slotProps">
|
||||
{{ formatDate(slotProps.data.dataact) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="nract" header="Nr. Act" style="width: 100px" />
|
||||
<Column field="nume" header="Partener" />
|
||||
<Column field="nume_cont_bancar" header="Cont" />
|
||||
<Column field="tip_registru" header="Tip">
|
||||
<template #body="slotProps">
|
||||
<Tag :value="slotProps.data.tip_registru" :severity="getRegisterSeverity(slotProps.data.tip_registru)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="incasari" header="Încasări">
|
||||
<template #body="slotProps">
|
||||
<span class="amount-green" v-if="slotProps.data.incasari > 0">
|
||||
{{ formatCurrency(slotProps.data.incasari, slotProps.data.valuta) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="plati" header="Plăți">
|
||||
<template #body="slotProps">
|
||||
<span class="amount-red" v-if="slotProps.data.plati > 0">
|
||||
{{ formatCurrency(slotProps.data.plati, slotProps.data.valuta) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="sold" header="Sold">
|
||||
<template #body="slotProps">
|
||||
<span :class="slotProps.data.sold >= 0 ? 'amount-green' : 'amount-red'">
|
||||
{{ formatCurrency(slotProps.data.sold, slotProps.data.valuta) }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="explicatia" header="Explicație" />
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useTreasuryStore } from "../stores/treasury";
|
||||
import { useCompanyStore } from "../stores/companies";
|
||||
import { format } from "date-fns";
|
||||
import { ro } from "date-fns/locale";
|
||||
|
||||
const treasuryStore = useTreasuryStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const filters = ref({
|
||||
dateFrom: null,
|
||||
dateTo: null,
|
||||
partnerName: ""
|
||||
});
|
||||
|
||||
const pagination = ref({
|
||||
page: 0,
|
||||
rows: 50
|
||||
});
|
||||
|
||||
const formatCurrency = (amount, currency = 'RON') => {
|
||||
if (!amount) return "0,00 " + currency;
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: currency
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return "";
|
||||
return format(new Date(dateString), "dd MMM yyyy", { locale: ro });
|
||||
};
|
||||
|
||||
const getRowClass = (data) => {
|
||||
return data.tip_registru.includes('BANCA') ? 'bank-row' : 'cash-row';
|
||||
};
|
||||
|
||||
const getRegisterSeverity = (type) => {
|
||||
if (type.includes('BANCA')) return 'info';
|
||||
if (type.includes('CASA')) return 'warning';
|
||||
return null;
|
||||
};
|
||||
|
||||
const onPage = (event) => {
|
||||
pagination.value = event;
|
||||
loadData();
|
||||
};
|
||||
|
||||
const applyFilters = () => {
|
||||
pagination.value.page = 0;
|
||||
loadData();
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
filters.value = {
|
||||
dateFrom: null,
|
||||
dateTo: null,
|
||||
partnerName: ""
|
||||
};
|
||||
loadData();
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
if (!companyStore.selectedCompany) return;
|
||||
|
||||
treasuryStore.setPagination(pagination.value);
|
||||
|
||||
await treasuryStore.loadBankCashRegister(
|
||||
companyStore.selectedCompany.id_firma,
|
||||
{
|
||||
date_from: filters.value.dateFrom?.toISOString().split("T")[0],
|
||||
date_to: filters.value.dateTo?.toISOString().split("T")[0],
|
||||
partner_name: filters.value.partnerName
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (companyStore.selectedCompany) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 1.5rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.view-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: var(--primary-color);
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 1.1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filters-card {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.filters-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-item label {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.summary-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--surface-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-icon.green {
|
||||
background: linear-gradient(135deg, var(--green-500), var(--green-600));
|
||||
}
|
||||
|
||||
.stat-icon.red {
|
||||
background: linear-gradient(135deg, var(--red-500), var(--red-600));
|
||||
}
|
||||
|
||||
.stat-details h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.stat-details p {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin: 0.25rem 0 0 0;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.amount-green {
|
||||
color: var(--green-600);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount-red {
|
||||
color: var(--red-600);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.bank-row) {
|
||||
background-color: var(--blue-50);
|
||||
}
|
||||
|
||||
:deep(.cash-row) {
|
||||
background-color: var(--yellow-50);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.filters-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.summary-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.filter-actions .p-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user