Shared Components: - Add CompanySelector.vue and PeriodSelector.vue components - Add AppHeader.vue and SlideMenu.vue layout components - Add shared stores factories (companies.js, accountingPeriod.js) - Add shared routes factories (companies.py, calendar.py) - Add shared models (company.py, calendar.py) - Add shared layout styles (header.css, navigation.css) Data Entry App: - Update CLAUDE.md with prod/test server documentation - Improve nomenclature sync service with better error handling - Update receipts router and CRUD operations - Add company/period stores using shared factories - Update App.vue layout with shared components - Fix OCRUploadZone file handling Reports App: - Refactor stores to use shared factories - Update App.vue to use shared layout components Infrastructure: - Replace start-data-entry.sh with separate dev/test scripts - Add .claude/rules for authentication, backend patterns, etc. - Add implementation plan for OCR receipt improvements - Clean up old documentation files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
159 lines
4.5 KiB
JavaScript
159 lines
4.5 KiB
JavaScript
/**
|
|
* Shared Accounting Period Store Factory
|
|
*
|
|
* Creates a Pinia store for accounting period selection that can be used by any ROA2WEB application.
|
|
* Each app passes its own apiService and store references.
|
|
*
|
|
* Usage:
|
|
* import { createAccountingPeriodStore } from '@shared/frontend/stores/accountingPeriod';
|
|
* import { apiService } from '../services/api';
|
|
* import { useAuthStore } from './auth';
|
|
* import { useCompanyStore } from './companies';
|
|
* export const useAccountingPeriodStore = createAccountingPeriodStore(apiService, useAuthStore, useCompanyStore);
|
|
*/
|
|
|
|
import { defineStore } from "pinia";
|
|
import { ref, computed } from "vue";
|
|
|
|
/**
|
|
* Factory function to create an accounting period store
|
|
* @param {Object} apiService - Axios instance configured for the app's API
|
|
* @param {Function} useAuthStore - Reference to the auth store function
|
|
* @param {Function} useCompanyStore - Reference to the company store function
|
|
* @returns {Function} Pinia store definition
|
|
*/
|
|
export function createAccountingPeriodStore(apiService, useAuthStore, useCompanyStore) {
|
|
return defineStore("accountingPeriod", () => {
|
|
// State
|
|
const periods = ref([]);
|
|
const selectedPeriod = ref(null);
|
|
const isLoading = ref(false);
|
|
const error = ref(null);
|
|
|
|
// Getters
|
|
const hasPeriods = computed(() => periods.value.length > 0);
|
|
const currentPeriod = computed(() => selectedPeriod.value);
|
|
|
|
// Computed date range for current period (first/last day of month)
|
|
const dateRange = computed(() => {
|
|
if (!selectedPeriod.value) return { dateFrom: null, dateTo: null };
|
|
|
|
const { an, luna } = selectedPeriod.value;
|
|
const firstDay = new Date(an, luna - 1, 1);
|
|
const lastDay = new Date(an, luna, 0);
|
|
|
|
return {
|
|
dateFrom: firstDay,
|
|
dateTo: lastDay,
|
|
};
|
|
});
|
|
|
|
// localStorage helpers
|
|
const getStorageKey = () => {
|
|
const authStore = useAuthStore();
|
|
const companyStore = useCompanyStore();
|
|
const username = authStore.user?.username;
|
|
const companyId = companyStore.selectedCompany?.id_firma;
|
|
if (!username || !companyId) return null;
|
|
return `selected_period_${username}_${companyId}`;
|
|
};
|
|
|
|
const initializeSelectedPeriod = () => {
|
|
const key = getStorageKey();
|
|
if (!key) return null;
|
|
|
|
const saved = localStorage.getItem(key);
|
|
if (saved) {
|
|
try {
|
|
return JSON.parse(saved);
|
|
} catch (e) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const persistSelectedPeriod = (period) => {
|
|
const key = getStorageKey();
|
|
if (key && period) {
|
|
localStorage.setItem(key, JSON.stringify(period));
|
|
}
|
|
};
|
|
|
|
// Actions
|
|
const loadPeriods = async (companyId) => {
|
|
if (!companyId) return { success: false };
|
|
|
|
isLoading.value = true;
|
|
error.value = null;
|
|
|
|
try {
|
|
const response = await apiService.get("/calendar/periods", {
|
|
params: { company: companyId },
|
|
});
|
|
|
|
periods.value = response.data.periods || [];
|
|
|
|
// Try to restore saved period or use most recent
|
|
const saved = initializeSelectedPeriod();
|
|
if (saved) {
|
|
const exists = periods.value.find(
|
|
(p) => p.an === saved.an && p.luna === saved.luna
|
|
);
|
|
if (exists) {
|
|
selectedPeriod.value = exists;
|
|
} else if (response.data.current_period) {
|
|
setSelectedPeriod(response.data.current_period);
|
|
}
|
|
} else if (response.data.current_period) {
|
|
setSelectedPeriod(response.data.current_period);
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (err) {
|
|
error.value = err.response?.data?.detail || "Failed to load periods";
|
|
return { success: false, error: error.value };
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const setSelectedPeriod = (period) => {
|
|
selectedPeriod.value = period;
|
|
persistSelectedPeriod(period);
|
|
};
|
|
|
|
const resetToLatest = () => {
|
|
if (periods.value.length > 0) {
|
|
setSelectedPeriod(periods.value[0]);
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
periods.value = [];
|
|
selectedPeriod.value = null;
|
|
isLoading.value = false;
|
|
error.value = null;
|
|
};
|
|
|
|
return {
|
|
// State
|
|
periods,
|
|
selectedPeriod,
|
|
isLoading,
|
|
error,
|
|
|
|
// Getters
|
|
hasPeriods,
|
|
currentPeriod,
|
|
dateRange,
|
|
|
|
// Actions
|
|
loadPeriods,
|
|
setSelectedPeriod,
|
|
resetToLatest,
|
|
reset,
|
|
};
|
|
});
|
|
}
|