feat: Add Trial Balance (Balanță de Verificare) feature

Comprehensive implementation of Trial Balance page with filtering,
pagination, and sorting capabilities.

Backend Changes:
- Added Pydantic models for Trial Balance (trial_balance.py)
  - TrialBalanceItem: Individual balance record
  - TrialBalanceFilters: Filter parameters
  - TrialBalancePagination: Pagination metadata
  - TrialBalanceResponse: Complete API response
- Created FastAPI router (/api/trial-balance) with:
  - Filtering by account number (cont) and description (denumire)
  - Pagination support (configurable page size)
  - Sorting on all columns (ascendent/descendent)
  - Company-based access control via JWT
  - Query against Oracle VBAL table
- Registered router in main.py

Frontend Changes:
- Created Pinia store (trialBalanceStore.js) with:
  - State management for trial balance data
  - Filters (luna, an, cont, denumire)
  - Pagination controls
  - Sorting functionality
  - Error handling and loading states
- Built TrialBalanceView.vue component featuring:
  - PrimeVue DataTable with responsive design
  - Period display (month/year)
  - Dual input filters (account number + description)
  - Debounced search (500ms)
  - Clear filters functionality
  - Formatted currency display (Romanian locale)
  - Balance columns (Debit/Credit) for:
    - Sold Precedent (Previous Balance)
    - Rulaj Lunar (Monthly Movement)
    - Sold Final (Final Balance)
  - Loading spinner and empty state
  - Mobile-friendly responsive layout
- Added route: /trial-balance with auth guard
- Added menu item in HamburgerMenu (Navigation section)
  - Icon: pi-calculator
  - Label: "Balanță de Verificare"

Technical Details:
- Follows established CSS architecture (no :deep(), uses design tokens)
- Consistent with InvoicesView patterns
- Implements proper error handling
- Uses Oracle NVL for null value handling
- ROW_NUMBER pagination for Oracle compatibility

Testing: Manual testing required (Phase 5)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 00:35:45 +02:00
parent 2b2002bbe8
commit 0b00b66ed5
7 changed files with 936 additions and 1 deletions

View File

@@ -0,0 +1,191 @@
/**
* Pinia Store for Trial Balance (Balanță de Verificare)
*/
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { apiService } from "../services/api";
export const useTrialBalanceStore = defineStore("trialBalance", () => {
// State
const trialBalanceData = ref([]);
const isLoading = ref(false);
const error = ref(null);
const filters = ref({
luna: new Date().getMonth() + 1, // Current month (1-12)
an: new Date().getFullYear(), // Current year
cont: "",
denumire: "",
});
const pagination = ref({
currentPage: 1,
pageSize: 50,
totalItems: 0,
totalPages: 0,
});
const sorting = ref({
sortBy: "CONT",
sortOrder: "asc",
});
// Getters
const hasData = computed(() => trialBalanceData.value.length > 0);
const currentPeriod = computed(() => {
return {
luna: filters.value.luna,
an: filters.value.an,
};
});
// Actions
const fetchTrialBalance = async (companyCode) => {
if (!companyCode) {
error.value = "Company code is required";
return { success: false, error: error.value };
}
isLoading.value = true;
error.value = null;
try {
const params = {
company: companyCode,
luna: filters.value.luna,
an: filters.value.an,
page: pagination.value.currentPage,
page_size: pagination.value.pageSize,
sort_by: sorting.value.sortBy,
sort_order: sorting.value.sortOrder,
};
// Add optional filters
if (filters.value.cont) {
params.cont_filter = filters.value.cont;
}
if (filters.value.denumire) {
params.denumire_filter = filters.value.denumire;
}
const response = await apiService.get("/trial-balance/", { params });
if (response.data.success) {
trialBalanceData.value = response.data.data.items || [];
// Update pagination
const paginationData = response.data.data.pagination;
pagination.value = {
currentPage: paginationData.current_page,
pageSize: paginationData.page_size,
totalItems: paginationData.total_items,
totalPages: paginationData.total_pages,
};
return { success: true };
} else {
throw new Error("Invalid response format");
}
} catch (err) {
error.value =
err.response?.data?.detail || "Failed to load trial balance data";
console.error("Failed to load trial balance:", err);
return { success: false, error: error.value };
} finally {
isLoading.value = false;
}
};
const applyFilters = async (newFilters, companyCode) => {
filters.value = { ...filters.value, ...newFilters };
pagination.value.currentPage = 1; // Reset to first page when filtering
await fetchTrialBalance(companyCode);
};
const clearFilters = async (companyCode) => {
filters.value = {
luna: new Date().getMonth() + 1,
an: new Date().getFullYear(),
cont: "",
denumire: "",
};
pagination.value.currentPage = 1;
await fetchTrialBalance(companyCode);
};
const changePage = async (page, companyCode) => {
pagination.value.currentPage = page;
await fetchTrialBalance(companyCode);
};
const changePageSize = async (pageSize, companyCode) => {
pagination.value.pageSize = pageSize;
pagination.value.currentPage = 1; // Reset to first page
await fetchTrialBalance(companyCode);
};
const sort = async (sortBy, sortOrder, companyCode) => {
sorting.value = { sortBy, sortOrder };
pagination.value.currentPage = 1; // Reset to first page when sorting
await fetchTrialBalance(companyCode);
};
const changePeriod = async (luna, an, companyCode) => {
filters.value.luna = luna;
filters.value.an = an;
pagination.value.currentPage = 1;
await fetchTrialBalance(companyCode);
};
const clearError = () => {
error.value = null;
};
const reset = () => {
trialBalanceData.value = [];
isLoading.value = false;
error.value = null;
filters.value = {
luna: new Date().getMonth() + 1,
an: new Date().getFullYear(),
cont: "",
denumire: "",
};
pagination.value = {
currentPage: 1,
pageSize: 50,
totalItems: 0,
totalPages: 0,
};
sorting.value = {
sortBy: "CONT",
sortOrder: "asc",
};
};
return {
// State
trialBalanceData,
isLoading,
error,
filters,
pagination,
sorting,
// Getters
hasData,
currentPeriod,
// Actions
fetchTrialBalance,
applyFilters,
clearFilters,
changePage,
changePageSize,
sort,
changePeriod,
clearError,
reset,
};
});