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
271 lines
8.1 KiB
JavaScript
271 lines
8.1 KiB
JavaScript
import { BasePage } from './BasePage.js';
|
|
|
|
export class PaymentsPage extends BasePage {
|
|
constructor(page) {
|
|
super(page);
|
|
|
|
// Page selectors
|
|
this.pageTitle = '.page-title';
|
|
this.pageSubtitle = '.page-subtitle';
|
|
|
|
// Company selection
|
|
this.companySelectionCard = '.company-selection-card';
|
|
this.companyDropdown = '.company-selection .p-dropdown';
|
|
this.companyDropdownTrigger = '.company-selection .p-dropdown-trigger';
|
|
this.companyOptions = '.p-dropdown-item';
|
|
|
|
// Search and filters
|
|
this.searchInput = '.search-input input';
|
|
this.methodFilter = '.method-filter .p-dropdown';
|
|
this.methodFilterTrigger = '.method-filter .p-dropdown-trigger';
|
|
this.dateRangeFilter = '.date-range-filter .p-dropdown';
|
|
this.dateRangeFilterTrigger = '.date-range-filter .p-dropdown-trigger';
|
|
this.refreshButton = '.refresh-button';
|
|
this.exportButton = '.export-button';
|
|
|
|
// View toggles
|
|
this.tableViewButton = '.table-view-button';
|
|
this.summaryViewButton = '.summary-view-button';
|
|
|
|
// Table selectors
|
|
this.paymentsTable = '.payments-table';
|
|
this.tableRows = '.payments-table tbody tr';
|
|
this.tableHeaders = '.payments-table thead th';
|
|
this.loadingSpinner = '.p-datatable-loading';
|
|
|
|
// Summary view
|
|
this.summaryView = '.payments-summary-view';
|
|
this.methodSummaryCards = '.method-summary-card';
|
|
|
|
// Totals card
|
|
this.totalsCard = '.payments-totals-card';
|
|
this.totalAmount = '.total-amount .amount-value';
|
|
this.totalCount = '.total-count .count-value';
|
|
|
|
// Pagination
|
|
this.pagination = '.p-paginator';
|
|
this.nextPageButton = '.p-paginator-next';
|
|
this.prevPageButton = '.p-paginator-prev';
|
|
this.currentPageSpan = '.p-paginator-current';
|
|
|
|
// Payment details
|
|
this.paymentDetailsModal = '.payment-details-modal';
|
|
this.paymentDetailsPanel = '.payment-details-panel';
|
|
|
|
// Specific table columns (adjust based on actual implementation)
|
|
this.referenceColumn = 'td:nth-child(1)';
|
|
this.dateColumn = 'td:nth-child(2)';
|
|
this.clientColumn = 'td:nth-child(3)';
|
|
this.amountColumn = 'td:nth-child(4)';
|
|
this.methodColumn = 'td:nth-child(5)';
|
|
}
|
|
|
|
async navigate() {
|
|
await this.page.goto('/payments');
|
|
await this.page.waitForSelector(this.pageTitle);
|
|
}
|
|
|
|
async isOnPaymentsPage() {
|
|
return await this.page.locator(this.pageTitle).isVisible();
|
|
}
|
|
|
|
async getPageTitle() {
|
|
return await this.page.locator(this.pageTitle).textContent();
|
|
}
|
|
|
|
async isCompanySelectionVisible() {
|
|
return await this.page.locator(this.companySelectionCard).isVisible();
|
|
}
|
|
|
|
async isPaymentsTableVisible() {
|
|
return await this.page.locator(this.paymentsTable).isVisible();
|
|
}
|
|
|
|
async selectCompany(companyName) {
|
|
await this.page.click(this.companyDropdownTrigger);
|
|
await this.page.waitForSelector(this.companyOptions);
|
|
await this.page.click(`${this.companyOptions}:has-text("${companyName}")`);
|
|
await this.waitForLoadingToFinish();
|
|
}
|
|
|
|
async searchPayments(searchTerm) {
|
|
await this.page.fill(this.searchInput, searchTerm);
|
|
await this.page.press(this.searchInput, 'Enter');
|
|
}
|
|
|
|
async filterByMethod(method) {
|
|
await this.page.click(this.methodFilterTrigger);
|
|
await this.page.waitForSelector(this.companyOptions);
|
|
|
|
// Map method to Romanian text (adjust based on actual implementation)
|
|
const methodMap = {
|
|
'bank_transfer': 'Transfer bancar',
|
|
'cash': 'Numerar',
|
|
'card': 'Card',
|
|
'check': 'Cec'
|
|
};
|
|
|
|
const methodText = methodMap[method] || method;
|
|
await this.page.click(`${this.companyOptions}:has-text("${methodText}")`);
|
|
}
|
|
|
|
async filterByDateRange(range) {
|
|
await this.page.click(this.dateRangeFilterTrigger);
|
|
await this.page.waitForSelector(this.companyOptions);
|
|
|
|
// Map range to Romanian text
|
|
const rangeMap = {
|
|
'thisMonth': 'Această lună',
|
|
'lastMonth': 'Luna trecută',
|
|
'thisYear': 'Acest an',
|
|
'custom': 'Personalizat'
|
|
};
|
|
|
|
const rangeText = rangeMap[range] || range;
|
|
await this.page.click(`${this.companyOptions}:has-text("${rangeText}")`);
|
|
}
|
|
|
|
async sortByColumn(columnName) {
|
|
// Map column names to actual header text
|
|
const columnMap = {
|
|
'reference': 'Referință',
|
|
'date': 'Data',
|
|
'client': 'Client',
|
|
'amount': 'Sumă',
|
|
'method': 'Metodă'
|
|
};
|
|
|
|
const headerText = columnMap[columnName] || columnName;
|
|
await this.page.click(`${this.tableHeaders}:has-text("${headerText}")`);
|
|
}
|
|
|
|
async getVisiblePaymentsCount() {
|
|
return await this.page.locator(this.tableRows).count();
|
|
}
|
|
|
|
async getFirstPaymentData() {
|
|
const firstRow = this.page.locator(this.tableRows).first();
|
|
|
|
return {
|
|
reference: await firstRow.locator(this.referenceColumn).textContent(),
|
|
date: await firstRow.locator(this.dateColumn).textContent(),
|
|
client: await firstRow.locator(this.clientColumn).textContent(),
|
|
amount: await firstRow.locator(this.amountColumn).textContent(),
|
|
method: await firstRow.locator(this.methodColumn).textContent()
|
|
};
|
|
}
|
|
|
|
async clickFirstPaymentRow() {
|
|
await this.page.locator(this.tableRows).first().click();
|
|
}
|
|
|
|
async isPaymentDetailsVisible() {
|
|
const modalVisible = await this.page.locator(this.paymentDetailsModal).isVisible();
|
|
const panelVisible = await this.page.locator(this.paymentDetailsPanel).isVisible();
|
|
return modalVisible || panelVisible;
|
|
}
|
|
|
|
async clickExportButton() {
|
|
await this.page.click(this.exportButton);
|
|
}
|
|
|
|
async clickRefreshButton() {
|
|
await this.page.click(this.refreshButton);
|
|
}
|
|
|
|
async isTotalsCardVisible() {
|
|
return await this.page.locator(this.totalsCard).isVisible();
|
|
}
|
|
|
|
async getTotalsData() {
|
|
return {
|
|
totalAmount: await this.page.locator(this.totalAmount).textContent(),
|
|
totalCount: await this.page.locator(this.totalCount).textContent()
|
|
};
|
|
}
|
|
|
|
async isSummaryViewAvailable() {
|
|
return await this.page.locator(this.summaryViewButton).isVisible();
|
|
}
|
|
|
|
async switchToSummaryView() {
|
|
await this.page.click(this.summaryViewButton);
|
|
}
|
|
|
|
async switchToTableView() {
|
|
await this.page.click(this.tableViewButton);
|
|
}
|
|
|
|
async isSummaryViewVisible() {
|
|
return await this.page.locator(this.summaryView).isVisible();
|
|
}
|
|
|
|
async isPaginationVisible() {
|
|
return await this.page.locator(this.pagination).isVisible();
|
|
}
|
|
|
|
async goToNextPage() {
|
|
await this.page.click(this.nextPageButton);
|
|
}
|
|
|
|
async goToPrevPage() {
|
|
await this.page.click(this.prevPageButton);
|
|
}
|
|
|
|
async getCurrentPage() {
|
|
const pageText = await this.page.locator(this.currentPageSpan).textContent();
|
|
// Extract page number from text like "Page 2 of 5"
|
|
const match = pageText.match(/(\d+)/);
|
|
return match ? parseInt(match[1]) : 1;
|
|
}
|
|
|
|
async waitForPageLoad() {
|
|
await Promise.race([
|
|
this.page.waitForSelector(this.companySelectionCard, { timeout: 10000 }),
|
|
this.page.waitForSelector(this.paymentsTable, { timeout: 10000 })
|
|
]);
|
|
}
|
|
|
|
async waitForTableLoad() {
|
|
await this.page.waitForSelector(this.paymentsTable, { timeout: 10000 });
|
|
await this.waitForLoadingToFinish();
|
|
}
|
|
|
|
async getPaymentByReference(reference) {
|
|
const rows = this.page.locator(this.tableRows);
|
|
const rowCount = await rows.count();
|
|
|
|
for (let i = 0; i < rowCount; i++) {
|
|
const row = rows.nth(i);
|
|
const ref = await row.locator(this.referenceColumn).textContent();
|
|
if (ref.trim() === reference) {
|
|
return row;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async clickPaymentByReference(reference) {
|
|
const row = await this.getPaymentByReference(reference);
|
|
if (row) {
|
|
await row.click();
|
|
}
|
|
}
|
|
|
|
async getMethodSummaryData() {
|
|
const summaryCards = this.page.locator(this.methodSummaryCards);
|
|
const cardCount = await summaryCards.count();
|
|
const data = [];
|
|
|
|
for (let i = 0; i < cardCount; i++) {
|
|
const card = summaryCards.nth(i);
|
|
const method = await card.locator('.method-name').textContent();
|
|
const amount = await card.locator('.method-amount').textContent();
|
|
const count = await card.locator('.method-count').textContent();
|
|
|
|
data.push({ method, amount, count });
|
|
}
|
|
|
|
return data;
|
|
}
|
|
} |