diff --git a/client/src/components/dataClassification/DataClassificationStage.tsx b/client/src/components/dataClassification/DataClassificationStage.tsx
new file mode 100644
index 0000000..36bc428
--- /dev/null
+++ b/client/src/components/dataClassification/DataClassificationStage.tsx
@@ -0,0 +1,111 @@
+import { useState } from 'react';
+import {
+ allClassified,
+ SEGMENT_TABS,
+ segmentsForType,
+ unclassifiedCount,
+} from './segments.ts';
+import { SegmentGrid } from './SegmentGrid.tsx';
+import {
+ chartStringSegmentsQueryOptions,
+ type ChartStringSegment,
+ useUpdateSegmentClassification,
+} from '@/queries/chartStringSegments.ts';
+import { Link } from '@tanstack/react-router';
+import { useQuery } from '@tanstack/react-query';
+
+export function DataClassificationStage() {
+ const { data: segments = [], isLoading } = useQuery(
+ chartStringSegmentsQueryOptions()
+ );
+ const updateClassification = useUpdateSegmentClassification();
+ const [activeType, setActiveType] = useState(SEGMENT_TABS[0].type);
+
+ if (isLoading) {
+ return
Loading segments...
;
+ }
+
+ const handleClassify = (
+ segment: ChartStringSegment,
+ includeInReport: boolean,
+ sfn: string | null
+ ) => {
+ updateClassification.mutate({
+ code: segment.code,
+ includeInReport,
+ segmentType: segment.segmentType,
+ sfn,
+ });
+ };
+
+ const gateOpen = allClassified(segments);
+
+ return (
+
+
+ New chart-string segments need to be classified before they can be
+ included or excluded from the AD419 report.
+
+
+
+ {SEGMENT_TABS.map((tab) => {
+ const count = unclassifiedCount(segments, tab.type);
+
+ return (
+
+ );
+ })}
+
+
+ {activeType === 'Ern' && (
+
+
+ Note: ERN code classification affects FTE calculations only. It does not
+ affect dollar-amount calculations.
+
+
+ )}
+
+
+
+
+
+ {gateOpen
+ ? 'All segments classified.'
+ : 'Unclassified rows must be set before the next step.'}
+
+ {gateOpen ? (
+
+ Continue to Expense Review
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/client/src/components/dataClassification/SegmentClassificationControl.tsx b/client/src/components/dataClassification/SegmentClassificationControl.tsx
new file mode 100644
index 0000000..fcd65ad
--- /dev/null
+++ b/client/src/components/dataClassification/SegmentClassificationControl.tsx
@@ -0,0 +1,73 @@
+import {
+ FUND_SFNS,
+ SFN_MULTIPLE,
+ type ChartStringSegment,
+} from '@/queries/chartStringSegments.ts';
+
+const EXCLUDED = 'Excluded';
+
+export function SegmentClassificationControl({
+ onClassify,
+ segment,
+}: {
+ onClassify: (includeInReport: boolean, sfn: string | null) => void;
+ segment: ChartStringSegment;
+}) {
+ if (segment.segmentType === 'Fund') {
+ const value =
+ segment.includeInReport === false
+ ? EXCLUDED
+ : (segment.sfn ?? '');
+
+ const handleChange = (selected: string) => {
+ if (selected === EXCLUDED) {
+ onClassify(false, null);
+ } else if (selected !== '') {
+ onClassify(true, selected);
+ }
+ };
+
+ return (
+
+ );
+ }
+
+ const status = segment.includeInReport;
+
+ return (
+
+
+
+ {status === null && (
+ Unset
+ )}
+
+ );
+}
diff --git a/client/src/components/dataClassification/SegmentGrid.tsx b/client/src/components/dataClassification/SegmentGrid.tsx
new file mode 100644
index 0000000..c3f7735
--- /dev/null
+++ b/client/src/components/dataClassification/SegmentGrid.tsx
@@ -0,0 +1,118 @@
+import { useState } from 'react';
+import { SegmentClassificationControl } from './SegmentClassificationControl.tsx';
+import type {
+ ChartStringSegment,
+ SegmentType,
+} from '@/queries/chartStringSegments.ts';
+import { DataTable } from '@/shared/dataTable.tsx';
+import type { ColumnDef } from '@tanstack/react-table';
+
+function classificationSortValue(segment: ChartStringSegment): string {
+ if (segment.includeInReport === null) {
+ return '';
+ }
+ return segment.includeInReport ? (segment.sfn ?? 'Included') : 'Excluded';
+}
+
+// Codes ordered with unclassified (unset) rows first, otherwise stable.
+function unsetFirstCodes(segments: ChartStringSegment[]): string[] {
+ return [...segments]
+ .sort(
+ (a, b) =>
+ (a.includeInReport === null ? 0 : 1) -
+ (b.includeInReport === null ? 0 : 1)
+ )
+ .map((segment) => segment.code);
+}
+
+export function SegmentGrid({
+ onClassify,
+ segments,
+ segmentType,
+}: {
+ onClassify: (
+ segment: ChartStringSegment,
+ includeInReport: boolean,
+ sfn: string | null
+ ) => void;
+ segments: ChartStringSegment[];
+ segmentType: SegmentType;
+}) {
+ const levelKeys = [
+ ...new Set(
+ segments.flatMap((segment) =>
+ segment.hierarchy.map((level) => level.level)
+ )
+ ),
+ ].sort();
+
+ const levelColumns: ColumnDef[] = levelKeys.map(
+ (levelKey) => ({
+ accessorFn: (segment) =>
+ segment.hierarchy.find((level) => level.level === levelKey)?.code ?? '',
+ cell: ({ row }) => {
+ const level = row.original.hierarchy.find(
+ (candidate) => candidate.level === levelKey
+ );
+ if (!level) {
+ return —;
+ }
+ return (
+
+ {level.code}
+
+ );
+ },
+ header: `Level ${levelKey}`,
+ id: `level-${levelKey}`,
+ })
+ );
+
+ const columns: ColumnDef[] = [
+ { accessorKey: 'code', header: 'Code' },
+ { accessorKey: 'description', header: 'Name' },
+ ...levelColumns,
+ {
+ accessorFn: classificationSortValue,
+ cell: ({ row }) => (
+
+ onClassify(row.original, includeInReport, sfn)
+ }
+ segment={row.original}
+ />
+ ),
+ header: 'Classification',
+ id: 'classification',
+ },
+ ];
+
+ // Freeze the default row order when the tab is entered: unclassified rows on top,
+ // otherwise stable. Recomputed only when the segment type changes (the "adjust
+ // state when a prop changes" pattern), so classifying a row in place does not
+ // immediately move it. Column sorting (below) overrides this when a header is
+ // clicked.
+ const [order, setOrder] = useState<{ codes: string[]; type: SegmentType }>(
+ () => ({ codes: unsetFirstCodes(segments), type: segmentType })
+ );
+ if (order.type !== segmentType) {
+ setOrder({ codes: unsetFirstCodes(segments), type: segmentType });
+ }
+ const orderIndex = new Map(order.codes.map((code, index) => [code, index]));
+ const orderedSegments = [...segments].sort(
+ (a, b) => (orderIndex.get(a.code) ?? 0) - (orderIndex.get(b.code) ?? 0)
+ );
+
+ return (
+
+ );
+}
diff --git a/client/src/components/dataClassification/segments.ts b/client/src/components/dataClassification/segments.ts
new file mode 100644
index 0000000..d9b092c
--- /dev/null
+++ b/client/src/components/dataClassification/segments.ts
@@ -0,0 +1,32 @@
+import type {
+ ChartStringSegment,
+ SegmentType,
+} from '@/queries/chartStringSegments.ts';
+
+export const SEGMENT_TABS: { label: string; type: SegmentType }[] = [
+ { label: 'Financial Dept', type: 'FinancialDepartment' },
+ { label: 'Natural Account', type: 'Account' },
+ { label: 'Fund', type: 'Fund' },
+ { label: 'Activity', type: 'Activity' },
+ { label: 'ERN', type: 'Ern' },
+];
+
+export function segmentsForType(
+ segments: ChartStringSegment[],
+ type: SegmentType
+): ChartStringSegment[] {
+ return segments.filter((segment) => segment.segmentType === type);
+}
+
+export function unclassifiedCount(
+ segments: ChartStringSegment[],
+ type: SegmentType
+): number {
+ return segmentsForType(segments, type).filter(
+ (segment) => segment.includeInReport === null
+ ).length;
+}
+
+export function allClassified(segments: ChartStringSegment[]): boolean {
+ return segments.every((segment) => segment.includeInReport !== null);
+}
diff --git a/client/src/queries/chartStringSegments.ts b/client/src/queries/chartStringSegments.ts
new file mode 100644
index 0000000..8674bca
--- /dev/null
+++ b/client/src/queries/chartStringSegments.ts
@@ -0,0 +1,83 @@
+import { fetchJson } from '../lib/api.ts';
+import {
+ queryOptions,
+ useMutation,
+ useQueryClient,
+} from '@tanstack/react-query';
+
+export type SegmentType =
+ | 'FinancialDepartment'
+ | 'Account'
+ | 'Fund'
+ | 'Activity'
+ | 'Ern';
+
+export interface HierarchyLevel {
+ code: string;
+ level: string;
+ name: string | null;
+}
+
+export interface ChartStringSegment {
+ code: string;
+ description: string | null;
+ hierarchy: HierarchyLevel[];
+ includeInReport: boolean | null;
+ segmentType: SegmentType;
+ sfn: string | null;
+}
+
+export const FUND_SFNS = ['201', '202', '203', '205', '220', '221', '223'] as const;
+export const SFN_MULTIPLE = 'Multiple';
+
+const SEGMENTS_KEY = ['chartStringSegments'] as const;
+
+export const chartStringSegmentsQueryOptions = () =>
+ queryOptions({
+ queryFn: () =>
+ fetchJson('/api/chartstringsegments'),
+ queryKey: SEGMENTS_KEY,
+ });
+
+export interface UpdateClassificationInput {
+ code: string;
+ includeInReport: boolean;
+ segmentType: SegmentType;
+ sfn: string | null;
+}
+
+export const useUpdateSegmentClassification = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (input: UpdateClassificationInput) =>
+ fetchJson('/api/chartstringsegments', {
+ body: JSON.stringify(input),
+ method: 'PATCH',
+ }),
+ onMutate: async (input) => {
+ await queryClient.cancelQueries({ queryKey: SEGMENTS_KEY });
+ const previous =
+ queryClient.getQueryData(SEGMENTS_KEY);
+
+ queryClient.setQueryData(SEGMENTS_KEY, (old) =>
+ (old ?? []).map((segment) =>
+ segment.segmentType === input.segmentType &&
+ segment.code === input.code
+ ? { ...segment, includeInReport: input.includeInReport, sfn: input.sfn }
+ : segment
+ )
+ );
+
+ return { previous };
+ },
+ // eslint-disable-next-line perfectionist/sort-objects
+ onError: (_error, _input, context) => {
+ if (context?.previous) {
+ queryClient.setQueryData(SEGMENTS_KEY, context.previous);
+ }
+ },
+ onSettled: () =>
+ queryClient.invalidateQueries({ queryKey: SEGMENTS_KEY }),
+ });
+};
diff --git a/client/src/routes/(authenticated)/workflow.$stageId.tsx b/client/src/routes/(authenticated)/workflow.$stageId.tsx
index 79791c5..3f0b8a8 100644
--- a/client/src/routes/(authenticated)/workflow.$stageId.tsx
+++ b/client/src/routes/(authenticated)/workflow.$stageId.tsx
@@ -3,6 +3,7 @@ import {
getCurrentAvailableStageId,
} from '@/mockData.ts';
import { workflowSnapshotQueryOptions } from '@/queries.ts';
+import { DataClassificationStage } from '@/components/dataClassification/DataClassificationStage.tsx';
import { SectionPanel } from '@/components/SectionPanel.tsx';
import { FlatFileImportPanel } from '@/components/FlatFileImportPanel.tsx';
import { WorkflowShell } from '@/components/WorkflowShell.tsx';
@@ -50,6 +51,8 @@ function WorkflowStageRoute() {
+ ) : workflowStageId === 'data-classification' ? (
+
) : (
This step is coming soon.
diff --git a/client/src/shared/dataTable.tsx b/client/src/shared/dataTable.tsx
index 3a92b07..b154846 100644
--- a/client/src/shared/dataTable.tsx
+++ b/client/src/shared/dataTable.tsx
@@ -50,6 +50,7 @@ export const DataTable = ({
// Keep this suppression until the library compatibility guidance changes.
// eslint-disable-next-line react-hooks/incompatible-library
const table = useReactTable({
+ autoResetPageIndex: false, // keep the current page when data updates in place
columns,
data,
getCoreRowModel: getCoreRowModel(), // basic rendering
diff --git a/client/src/test/components/dataClassification/SegmentClassificationControl.test.tsx b/client/src/test/components/dataClassification/SegmentClassificationControl.test.tsx
new file mode 100644
index 0000000..8068fd4
--- /dev/null
+++ b/client/src/test/components/dataClassification/SegmentClassificationControl.test.tsx
@@ -0,0 +1,69 @@
+import { describe, expect, it, vi } from 'vitest';
+import { fireEvent, render, screen } from '@testing-library/react';
+import type { ChartStringSegment } from '@/queries/chartStringSegments.ts';
+import { SegmentClassificationControl } from '@/components/dataClassification/SegmentClassificationControl.tsx';
+
+const unsetAccount: ChartStringSegment = {
+ code: '500000',
+ description: 'Supplies',
+ hierarchy: [],
+ includeInReport: null,
+ segmentType: 'Account',
+ sfn: null,
+};
+
+const unsetFund: ChartStringSegment = {
+ code: '70575',
+ description: 'Berry',
+ hierarchy: [],
+ includeInReport: null,
+ segmentType: 'Fund',
+ sfn: null,
+};
+
+describe('SegmentClassificationControl', () => {
+ it('calls onClassify with (true, null) when Include is clicked on a non-fund', () => {
+ const onClassify = vi.fn();
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'Include' }));
+
+ expect(onClassify).toHaveBeenCalledWith(true, null);
+ });
+
+ it('renders a dropdown with SFN options for a fund', () => {
+ render();
+
+ expect(screen.getByRole('option', { name: '201' })).toBeInTheDocument();
+ expect(screen.getByRole('option', { name: 'Multiple' })).toBeInTheDocument();
+ expect(screen.getByRole('option', { name: 'Excluded' })).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: 'Include' })).not.toBeInTheDocument();
+ });
+
+ it('calls onClassify with (true, sfn) when an SFN is selected', () => {
+ const onClassify = vi.fn();
+ render();
+
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: '220' } });
+
+ expect(onClassify).toHaveBeenCalledWith(true, '220');
+ });
+
+ it('calls onClassify with (true, "Multiple") when Multiple is selected', () => {
+ const onClassify = vi.fn();
+ render();
+
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'Multiple' } });
+
+ expect(onClassify).toHaveBeenCalledWith(true, 'Multiple');
+ });
+
+ it('calls onClassify with (false, null) when Excluded is selected', () => {
+ const onClassify = vi.fn();
+ render();
+
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'Excluded' } });
+
+ expect(onClassify).toHaveBeenCalledWith(false, null);
+ });
+});
diff --git a/client/src/test/components/dataClassification/SegmentGrid.test.tsx b/client/src/test/components/dataClassification/SegmentGrid.test.tsx
new file mode 100644
index 0000000..adfd935
--- /dev/null
+++ b/client/src/test/components/dataClassification/SegmentGrid.test.tsx
@@ -0,0 +1,98 @@
+import { describe, expect, it, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import type { ChartStringSegment } from '@/queries/chartStringSegments.ts';
+import { SegmentGrid } from '@/components/dataClassification/SegmentGrid.tsx';
+
+function account(
+ code: string,
+ includeInReport: boolean | null
+): ChartStringSegment {
+ return { code, description: `Name ${code}`, hierarchy: [], includeInReport, segmentType: 'Account', sfn: null };
+}
+
+// True when `first` appears before `second` in the DOM.
+function isBefore(first: HTMLElement, second: HTMLElement): boolean {
+ return Boolean(
+ first.compareDocumentPosition(second) & Node.DOCUMENT_POSITION_FOLLOWING
+ );
+}
+
+const fundSegments: ChartStringSegment[] = [
+ {
+ code: '45530',
+ description: 'AES State Appropriations',
+ hierarchy: [
+ { code: 'STATE', level: '0', name: 'State Funds' },
+ { code: 'APPROP', level: '1', name: 'Appropriations' },
+ ],
+ includeInReport: true,
+ segmentType: 'Fund',
+ sfn: '220',
+ },
+];
+
+const accountSegments: ChartStringSegment[] = [
+ {
+ code: '500000',
+ description: 'Supplies and Expense',
+ hierarchy: [],
+ includeInReport: null,
+ segmentType: 'Account',
+ sfn: null,
+ },
+];
+
+describe('SegmentGrid', () => {
+ it('renders a column per hierarchy level with the code and hover title', () => {
+ render();
+
+ expect(screen.getByRole('columnheader', { name: 'Level 0' })).toBeInTheDocument();
+ expect(screen.getByRole('columnheader', { name: 'Level 1' })).toBeInTheDocument();
+
+ const state = screen.getByText('STATE');
+ expect(state).toHaveAttribute('data-tip', 'State Funds');
+ expect(state).toHaveClass('cursor-help');
+ expect(screen.getByText('APPROP')).toHaveAttribute('data-tip', 'Appropriations');
+ });
+
+ it('renders no level columns when no segment has a hierarchy', () => {
+ render();
+
+ expect(screen.queryByRole('columnheader', { name: /^Level / })).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Include' })).toBeInTheDocument();
+ });
+
+ it('sorts unclassified rows to the top by default', () => {
+ render(
+
+ );
+
+ // BBB is unclassified, so it renders before the classified AAA.
+ expect(isBefore(screen.getByText('BBB'), screen.getByText('AAA'))).toBe(true);
+ });
+
+ it('keeps a row in place when it is classified in the same tab', () => {
+ const { rerender } = render(
+
+ );
+ expect(isBefore(screen.getByText('BBB'), screen.getByText('AAA'))).toBe(true);
+
+ // BBB gets classified: order is frozen for the tab, so it stays above AAA.
+ rerender(
+
+ );
+ expect(isBefore(screen.getByText('BBB'), screen.getByText('AAA'))).toBe(true);
+ });
+});
diff --git a/client/src/test/components/dataClassification/segments.test.ts b/client/src/test/components/dataClassification/segments.test.ts
new file mode 100644
index 0000000..0d5610f
--- /dev/null
+++ b/client/src/test/components/dataClassification/segments.test.ts
@@ -0,0 +1,43 @@
+import { describe, expect, it } from 'vitest';
+import type { ChartStringSegment } from '@/queries/chartStringSegments.ts';
+import {
+ allClassified,
+ SEGMENT_TABS,
+ segmentsForType,
+ unclassifiedCount,
+} from '@/components/dataClassification/segments.ts';
+
+const segments: ChartStringSegment[] = [
+ { code: '45530', description: 'AES', hierarchy: [], includeInReport: true, segmentType: 'Fund', sfn: '220' },
+ { code: '70575', description: 'Berry', hierarchy: [], includeInReport: null, segmentType: 'Fund', sfn: '219' },
+ { code: '500000', description: 'S and E', hierarchy: [], includeInReport: null, segmentType: 'Account', sfn: null },
+];
+
+describe('data classification segment helpers', () => {
+ it('exposes the tabs in display order, including ERN', () => {
+ expect(SEGMENT_TABS.map((tab) => tab.type)).toEqual([
+ 'FinancialDepartment',
+ 'Account',
+ 'Fund',
+ 'Activity',
+ 'Ern',
+ ]);
+ expect(SEGMENT_TABS.at(-1)).toEqual({ label: 'ERN', type: 'Ern' });
+ });
+
+ it('filters segments by type', () => {
+ expect(segmentsForType(segments, 'Fund')).toHaveLength(2);
+ expect(segmentsForType(segments, 'Account')).toHaveLength(1);
+ });
+
+ it('counts unclassified segments per type', () => {
+ expect(unclassifiedCount(segments, 'Fund')).toBe(1);
+ expect(unclassifiedCount(segments, 'Account')).toBe(1);
+ expect(unclassifiedCount(segments, 'Activity')).toBe(0);
+ });
+
+ it('reports the gate as closed while any segment is unclassified', () => {
+ expect(allClassified(segments)).toBe(false);
+ expect(allClassified(segments.filter((s) => s.includeInReport !== null))).toBe(true);
+ });
+});
diff --git a/client/src/test/routes/(authenticated)/dataClassification.test.tsx b/client/src/test/routes/(authenticated)/dataClassification.test.tsx
new file mode 100644
index 0000000..4371379
--- /dev/null
+++ b/client/src/test/routes/(authenticated)/dataClassification.test.tsx
@@ -0,0 +1,102 @@
+import { describe, expect, it } from 'vitest';
+import { http, HttpResponse } from 'msw';
+import { fireEvent, screen, waitFor } from '@testing-library/react';
+import { server } from '@/test/mswUtils.ts';
+import { renderRoute } from '@/test/routerUtils.tsx';
+
+const mockUser = {
+ email: 'shannon@example.edu',
+ id: 'user-1',
+ name: 'Shannon Taylor',
+ roles: ['User'],
+};
+
+const segments: {
+ code: string;
+ description: string;
+ hierarchy: { code: string; level: string; name: string | null }[];
+ includeInReport: boolean | null;
+ segmentType: string;
+ sfn: string | null;
+}[] = [
+ { code: '45530', description: 'AES', hierarchy: [], includeInReport: true, segmentType: 'Fund', sfn: '220' },
+ { code: '70575', description: 'Berry', hierarchy: [], includeInReport: null, segmentType: 'Fund', sfn: null },
+];
+
+function mockApi() {
+ let current = [...segments];
+ server.use(
+ http.get('/api/user/me', () => HttpResponse.json(mockUser)),
+ http.get('/api/chartstringsegments', () => HttpResponse.json(current)),
+ http.patch('/api/chartstringsegments', async ({ request }) => {
+ const body = await request.json() as { code: string; includeInReport: boolean; segmentType: string; sfn: string | null };
+ current = current.map((s) =>
+ s.code === body.code ? { ...s, includeInReport: body.includeInReport } : s
+ );
+ return new HttpResponse(null, { status: 204 });
+ })
+ );
+}
+
+describe('Data Classification stage', () => {
+ it('renders the classifier with tabs instead of the placeholder', async () => {
+ mockApi();
+ const { cleanup } = renderRoute({ initialPath: '/workflow/data-classification' });
+
+ try {
+ expect(await screen.findByRole('tab', { name: /Fund/ })).toBeInTheDocument();
+ expect(screen.getByRole('tab', { name: /Financial Dept/ })).toBeInTheDocument();
+ expect(screen.queryByRole('heading', { name: 'Coming soon' })).not.toBeInTheDocument();
+ } finally {
+ cleanup();
+ }
+ });
+
+ it('blocks Continue until every segment is classified, then opens the gate', async () => {
+ mockApi();
+ const { cleanup } = renderRoute({ initialPath: '/workflow/data-classification' });
+
+ try {
+ const fundTab = await screen.findByRole('tab', { name: /Fund/ });
+ fireEvent.click(fundTab);
+
+ await screen.findByText('70575');
+
+ expect(
+ screen.getByRole('button', { name: /Continue to Expense Review/ })
+ ).toBeDisabled();
+
+ const dropdowns = screen.getAllByRole('combobox');
+ const unsetDropdown = dropdowns.find((dropdown) => dropdown.value === '');
+ fireEvent.change(unsetDropdown!, { target: { value: '201' } });
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('link', { name: /Continue to Expense Review/ })
+ ).toBeInTheDocument();
+ });
+ } finally {
+ cleanup();
+ }
+ });
+
+ it('shows the FTE disclaimer only on the ERN tab', async () => {
+ mockApi();
+ const { cleanup } = renderRoute({ initialPath: '/workflow/data-classification' });
+
+ try {
+ await screen.findByRole('tab', { name: /Fund/ });
+ expect(
+ screen.queryByText(/affects fte calculations only/i)
+ ).not.toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('tab', { name: /ERN/ }));
+
+ expect(
+ await screen.findByText(/affects fte calculations only/i)
+ ).toBeInTheDocument();
+ } finally {
+ cleanup();
+ }
+ });
+});
diff --git a/client/src/test/setup.ts b/client/src/test/setup.ts
index bb02c60..1d55db9 100644
--- a/client/src/test/setup.ts
+++ b/client/src/test/setup.ts
@@ -1 +1,5 @@
import '@testing-library/jest-dom/vitest';
+import { cleanup } from '@testing-library/react';
+import { afterEach } from 'vitest';
+
+afterEach(cleanup);
diff --git a/database/data/Tables/AccountHierarchy.sql b/database/data/Tables/AccountHierarchy.sql
new file mode 100644
index 0000000..cddaff6
--- /dev/null
+++ b/database/data/Tables/AccountHierarchy.sql
@@ -0,0 +1,19 @@
+CREATE TABLE [data].[AccountHierarchy]
+(
+ [Code] NVARCHAR(50) NOT NULL,
+ [Description] NVARCHAR(1000) NULL,
+ [ParentLevel0Code] VARCHAR(20) NULL,
+ [ParentLevel0Name] NVARCHAR(1000) NULL,
+ [ParentLevel1Code] VARCHAR(20) NULL,
+ [ParentLevel1Name] NVARCHAR(1000) NULL,
+ [ParentLevel2Code] VARCHAR(20) NULL,
+ [ParentLevel2Name] NVARCHAR(1000) NULL,
+ [ParentLevel3Code] VARCHAR(20) NULL,
+ [ParentLevel3Name] NVARCHAR(1000) NULL,
+ [ParentLevel4Code] VARCHAR(20) NULL,
+ [ParentLevel4Name] NVARCHAR(1000) NULL,
+ [ParentLevel5Code] VARCHAR(20) NULL,
+ [ParentLevel5Name] NVARCHAR(1000) NULL,
+ [LoadedAt] DATETIME2(3) NOT NULL CONSTRAINT [DF_AccountHierarchy_LoadedAt] DEFAULT (SYSUTCDATETIME()),
+ CONSTRAINT [PK_AccountHierarchy] PRIMARY KEY CLUSTERED ([Code])
+);
diff --git a/database/data/Tables/ActivityHierarchy.sql b/database/data/Tables/ActivityHierarchy.sql
new file mode 100644
index 0000000..e7ff8bf
--- /dev/null
+++ b/database/data/Tables/ActivityHierarchy.sql
@@ -0,0 +1,19 @@
+CREATE TABLE [data].[ActivityHierarchy]
+(
+ [Code] NVARCHAR(50) NOT NULL,
+ [Description] NVARCHAR(1000) NULL,
+ [ParentLevel0Code] VARCHAR(20) NULL,
+ [ParentLevel0Name] NVARCHAR(1000) NULL,
+ [ParentLevel1Code] VARCHAR(20) NULL,
+ [ParentLevel1Name] NVARCHAR(1000) NULL,
+ [ParentLevel2Code] VARCHAR(20) NULL,
+ [ParentLevel2Name] NVARCHAR(1000) NULL,
+ [ParentLevel3Code] VARCHAR(20) NULL,
+ [ParentLevel3Name] NVARCHAR(1000) NULL,
+ [ParentLevel4Code] VARCHAR(20) NULL,
+ [ParentLevel4Name] NVARCHAR(1000) NULL,
+ [ParentLevel5Code] VARCHAR(20) NULL,
+ [ParentLevel5Name] NVARCHAR(1000) NULL,
+ [LoadedAt] DATETIME2(3) NOT NULL CONSTRAINT [DF_ActivityHierarchy_LoadedAt] DEFAULT (SYSUTCDATETIME()),
+ CONSTRAINT [PK_ActivityHierarchy] PRIMARY KEY CLUSTERED ([Code])
+);
diff --git a/database/data/Tables/ChartStringSegments.sql b/database/data/Tables/ChartStringSegments.sql
index 66a07d0..e1116d7 100644
--- a/database/data/Tables/ChartStringSegments.sql
+++ b/database/data/Tables/ChartStringSegments.sql
@@ -4,7 +4,7 @@ CREATE TABLE [data].[ChartStringSegments]
[Code] NVARCHAR(50) NOT NULL,
[Description] NVARCHAR(300) NULL,
[IncludeInReport] BIT NULL, -- NULL = unclassified, needs review
- [Sfn] NVARCHAR(3) NULL, -- only meaningful for Fund
+ [Sfn] NVARCHAR(10) NULL, -- SFN code (201..223) or 'Multiple'; only for Fund
CONSTRAINT [PK_ChartStringSegments] PRIMARY KEY CLUSTERED ([SegmentType], [Code]),
CONSTRAINT [CK_ChartStringSegments_Sfn] CHECK ([Sfn] IS NULL OR [SegmentType] = 'Fund')
);
diff --git a/database/data/Tables/DepartmentHierarchy.sql b/database/data/Tables/DepartmentHierarchy.sql
new file mode 100644
index 0000000..e5a584e
--- /dev/null
+++ b/database/data/Tables/DepartmentHierarchy.sql
@@ -0,0 +1,21 @@
+CREATE TABLE [data].[DepartmentHierarchy]
+(
+ [Code] NVARCHAR(50) NOT NULL,
+ [Description] NVARCHAR(1000) NULL,
+ [ParentLevelACode] VARCHAR(20) NULL,
+ [ParentLevelAName] NVARCHAR(1000) NULL,
+ [ParentLevelBCode] VARCHAR(20) NULL,
+ [ParentLevelBName] NVARCHAR(1000) NULL,
+ [ParentLevelCCode] VARCHAR(20) NULL,
+ [ParentLevelCName] NVARCHAR(1000) NULL,
+ [ParentLevelDCode] VARCHAR(20) NULL,
+ [ParentLevelDName] NVARCHAR(1000) NULL,
+ [ParentLevelECode] VARCHAR(20) NULL,
+ [ParentLevelEName] NVARCHAR(1000) NULL,
+ [ParentLevelFCode] VARCHAR(20) NULL,
+ [ParentLevelFName] NVARCHAR(1000) NULL,
+ [ParentLevelGCode] VARCHAR(20) NULL,
+ [ParentLevelGName] NVARCHAR(1000) NULL,
+ [LoadedAt] DATETIME2(3) NOT NULL CONSTRAINT [DF_DepartmentHierarchy_LoadedAt] DEFAULT (SYSUTCDATETIME()),
+ CONSTRAINT [PK_DepartmentHierarchy] PRIMARY KEY CLUSTERED ([Code])
+);
diff --git a/database/data/Tables/FundHierarchy.sql b/database/data/Tables/FundHierarchy.sql
new file mode 100644
index 0000000..21812c8
--- /dev/null
+++ b/database/data/Tables/FundHierarchy.sql
@@ -0,0 +1,19 @@
+CREATE TABLE [data].[FundHierarchy]
+(
+ [Code] NVARCHAR(50) NOT NULL,
+ [Description] NVARCHAR(1000) NULL,
+ [ParentLevel0Code] VARCHAR(20) NULL,
+ [ParentLevel0Name] NVARCHAR(1000) NULL,
+ [ParentLevel1Code] VARCHAR(20) NULL,
+ [ParentLevel1Name] NVARCHAR(1000) NULL,
+ [ParentLevel2Code] VARCHAR(20) NULL,
+ [ParentLevel2Name] NVARCHAR(1000) NULL,
+ [ParentLevel3Code] VARCHAR(20) NULL,
+ [ParentLevel3Name] NVARCHAR(1000) NULL,
+ [ParentLevel4Code] VARCHAR(20) NULL,
+ [ParentLevel4Name] NVARCHAR(1000) NULL,
+ [ParentLevel5Code] VARCHAR(20) NULL,
+ [ParentLevel5Name] NVARCHAR(1000) NULL,
+ [LoadedAt] DATETIME2(3) NOT NULL CONSTRAINT [DF_FundHierarchy_LoadedAt] DEFAULT (SYSUTCDATETIME()),
+ CONSTRAINT [PK_FundHierarchy] PRIMARY KEY CLUSTERED ([Code])
+);
diff --git a/server.core/Data/AppDbContext.cs b/server.core/Data/AppDbContext.cs
index 2db0191..078bab5 100644
--- a/server.core/Data/AppDbContext.cs
+++ b/server.core/Data/AppDbContext.cs
@@ -6,11 +6,19 @@ namespace Server.Core.Data;
public class AppDbContext(DbContextOptions options) : DbContext(options)
{
public const string AppSchema = "app";
+ public const string DataSchema = "data";
public const string MigrationsHistoryTable = "__EFMigrationsHistory";
public DbSet ImportLogs => Set();
public DbSet Users => Set();
+ public DbSet ChartStringSegments => Set();
+
+ public DbSet DepartmentHierarchies => Set();
+ public DbSet AccountHierarchies => Set();
+ public DbSet FundHierarchies => Set();
+ public DbSet ActivityHierarchies => Set();
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
@@ -18,5 +26,44 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.HasDefaultSchema(AppSchema);
modelBuilder.Entity().ToTable("ImportLog", AppSchema);
modelBuilder.Entity().ToTable("Users", AppSchema);
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.ToTable("ChartStringSegments", DataSchema, table => table.ExcludeFromMigrations());
+ entity.HasKey(segment => new { segment.SegmentType, segment.Code });
+ entity.Property(segment => segment.SegmentType).HasConversion().HasMaxLength(20);
+ entity.Property(segment => segment.Code).HasMaxLength(50);
+ entity.Property(segment => segment.Description).HasMaxLength(300);
+ entity.Property(segment => segment.Sfn).HasMaxLength(10);
+ });
+
+ ConfigureHierarchy(modelBuilder, "DepartmentHierarchy");
+ ConfigureHierarchy(modelBuilder, "AccountHierarchy");
+ ConfigureHierarchy(modelBuilder, "FundHierarchy");
+ ConfigureHierarchy(modelBuilder, "ActivityHierarchy");
+ }
+
+ private static void ConfigureHierarchy(ModelBuilder modelBuilder, string table)
+ where T : class, ISegmentHierarchy
+ {
+ modelBuilder.Entity(entity =>
+ {
+ entity.ToTable(table, DataSchema, t => t.ExcludeFromMigrations());
+ entity.HasKey(e => e.Code);
+
+ foreach (var property in entity.Metadata.GetProperties()
+ .Where(p => p.ClrType == typeof(string)))
+ {
+ var maxLength = property.Name switch
+ {
+ // Match ChartStringSegment.Code so joins/lookups on Code stay aligned.
+ nameof(ISegmentHierarchy.Code) => 50,
+ "Description" => 1000,
+ _ when property.Name.EndsWith("Name") => 1000,
+ _ => 20,
+ };
+ property.SetMaxLength(maxLength);
+ }
+ });
}
}
diff --git a/server.core/Data/ChartStringSegmentSeed.cs b/server.core/Data/ChartStringSegmentSeed.cs
new file mode 100644
index 0000000..381e09b
--- /dev/null
+++ b/server.core/Data/ChartStringSegmentSeed.cs
@@ -0,0 +1,107 @@
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Domain;
+
+namespace Server.Core.Data;
+
+///
+/// Seeds the chart-string segments that need classifying, derived from the hierarchy
+/// tables so every segment has a matching breadcrumb. Runs after .
+/// Segments start unclassified (IncludeInReport = null); a couple per type are
+/// pre-classified so the grid shows a mix of unset and classified rows.
+///
+public static class ChartStringSegmentSeed
+{
+ // Leave roughly this many rows per segment type unclassified so there is still
+ // work to do; the rest are classified (include/exclude) below.
+ private const int UnsetPerType = 10;
+
+ // Valid SFN values (mirrors the server's FundSfns / client FUND_SFNS list, plus the
+ // "Multiple" marker). Used to give seeded, included funds a real SFN so the Fund
+ // dropdown reflects a classified state rather than reading "Unset".
+ private static readonly string[] FundSfnPool =
+ ["201", "202", "203", "205", "220", "221", "223", "Multiple"];
+
+ public static async Task EnsureSeededAsync(AppDbContext db, CancellationToken ct = default)
+ {
+ if (await db.ChartStringSegments.AnyAsync(ct))
+ {
+ return;
+ }
+
+ var segments = new List();
+ segments.AddRange(await BuildAsync(db.AccountHierarchies, SegmentType.Account, ct));
+ segments.AddRange(await BuildAsync(db.FundHierarchies, SegmentType.Fund, ct));
+ segments.AddRange(await BuildAsync(db.ActivityHierarchies, SegmentType.Activity, ct));
+ segments.AddRange(await BuildAsync(db.DepartmentHierarchies, SegmentType.FinancialDepartment, ct));
+ segments.AddRange(BuildErn());
+
+ db.ChartStringSegments.AddRange(segments);
+ await db.SaveChangesAsync(ct);
+ }
+
+ // ErnCodes.csv columns: 0 DOS_Code, 1 Description, 2 IncludeInAD419FTE, 3 IsNewInUCP.
+ // IncludeInAD419FTE seeds the classification directly, except the first few (by DOS
+ // code) which stay unset for demo. IsNewInUCP is not consumed yet.
+ private static List BuildErn()
+ {
+ var rows = SeedCsv.ReadRows("ErnCodes.csv", fields => (
+ Code: fields[0].Trim(),
+ Description: SeedCsv.Nullable(fields[1]),
+ Include: string.Equals(fields[2].Trim(), "true", StringComparison.OrdinalIgnoreCase)));
+
+ return rows
+ .OrderBy(row => row.Code, StringComparer.Ordinal)
+ .Select((row, index) => new ChartStringSegment
+ {
+ SegmentType = SegmentType.Ern,
+ Code = row.Code,
+ Description = row.Description,
+ IncludeInReport = index < UnsetPerType ? null : row.Include,
+ })
+ .ToList();
+ }
+
+ private static async Task> BuildAsync(
+ DbSet hierarchy,
+ SegmentType type,
+ CancellationToken ct)
+ where T : class, ISegmentHierarchy
+ {
+ var rows = await hierarchy.ToListAsync(ct);
+ return rows
+ .OrderBy(row => row.Code, StringComparer.Ordinal)
+ .Select((row, index) =>
+ {
+ // First few stay unset; the rest are classified include/exclude with a
+ // stable pseudo-random split so dev data looks partially worked.
+ bool? include = index < UnsetPerType ? null : (StableHash(row.Code) & 1) == 0;
+
+ // An included fund must carry a valid SFN, otherwise the Fund dropdown
+ // has no matching option and shows "Unset" despite being classified.
+ var sfn = type == SegmentType.Fund && include == true
+ ? FundSfnPool[(StableHash(row.Code) & int.MaxValue) % FundSfnPool.Length]
+ : null;
+
+ return new ChartStringSegment
+ {
+ SegmentType = type,
+ Code = row.Code,
+ Description = row.Description,
+ IncludeInReport = include,
+ Sfn = sfn,
+ };
+ })
+ .ToList();
+ }
+
+ private static int StableHash(string value)
+ {
+ var hash = 17;
+ foreach (var c in value)
+ {
+ hash = (hash * 31) + c;
+ }
+
+ return hash;
+ }
+}
diff --git a/server.core/Data/DbInitializer.cs b/server.core/Data/DbInitializer.cs
index 50481ec..042421a 100644
--- a/server.core/Data/DbInitializer.cs
+++ b/server.core/Data/DbInitializer.cs
@@ -34,8 +34,12 @@ public async Task InitializeAsync(bool includeDevSeed, CancellationToken cancell
}
}
- private Task SeedDevelopmentAsync(CancellationToken ct)
- => Task.CompletedTask;
+ private async Task SeedDevelopmentAsync(CancellationToken ct)
+ {
+ // Hierarchy first: chart-string segments are derived from the hierarchy tables.
+ await HierarchySeed.EnsureSeededAsync(_db, ct);
+ await ChartStringSegmentSeed.EnsureSeededAsync(_db, ct);
+ }
// just a placeholder for any production-safe seeding
private Task SeedProductionSafeAsync(CancellationToken ct)
diff --git a/server.core/Data/HierarchySeed.cs b/server.core/Data/HierarchySeed.cs
new file mode 100644
index 0000000..841bf8b
--- /dev/null
+++ b/server.core/Data/HierarchySeed.cs
@@ -0,0 +1,95 @@
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Domain;
+
+namespace Server.Core.Data;
+
+///
+/// Seeds the four chart-segment hierarchy tables from embedded CSV resources
+/// (server.core/Data/Seed/*.csv). The Account/Fund/Activity CSVs map directly to
+/// their tables. Department has no source hierarchy of its own, so it is derived
+/// from DepartmentSource.csv: levels D-G come from the FinancialDept columns and
+/// levels A-C are synthesized (UC Davis > College > Experiment Station).
+///
+public static class HierarchySeed
+{
+ public static async Task EnsureSeededAsync(AppDbContext db, CancellationToken ct = default)
+ {
+ if (!await db.AccountHierarchies.AnyAsync(ct))
+ {
+ db.AccountHierarchies.AddRange(SeedCsv.ReadRows("AccountHierarchy.csv", AccountFrom));
+ }
+
+ if (!await db.FundHierarchies.AnyAsync(ct))
+ {
+ db.FundHierarchies.AddRange(SeedCsv.ReadRows("FundHierarchy.csv", FundFrom));
+ }
+
+ if (!await db.ActivityHierarchies.AnyAsync(ct))
+ {
+ db.ActivityHierarchies.AddRange(SeedCsv.ReadRows("ActivityHierarchy.csv", ActivityFrom));
+ }
+
+ if (!await db.DepartmentHierarchies.AnyAsync(ct))
+ {
+ db.DepartmentHierarchies.AddRange(DepartmentRows());
+ }
+
+ await db.SaveChangesAsync(ct);
+ }
+
+ private static AccountHierarchy AccountFrom(string[] f) => new()
+ {
+ Code = f[0],
+ Description = SeedCsv.Nullable(f[1]),
+ ParentLevel0Code = SeedCsv.Nullable(f[2]), ParentLevel0Name = SeedCsv.Nullable(f[3]),
+ ParentLevel1Code = SeedCsv.Nullable(f[4]), ParentLevel1Name = SeedCsv.Nullable(f[5]),
+ ParentLevel2Code = SeedCsv.Nullable(f[6]), ParentLevel2Name = SeedCsv.Nullable(f[7]),
+ ParentLevel3Code = SeedCsv.Nullable(f[8]), ParentLevel3Name = SeedCsv.Nullable(f[9]),
+ ParentLevel4Code = SeedCsv.Nullable(f[10]), ParentLevel4Name = SeedCsv.Nullable(f[11]),
+ ParentLevel5Code = SeedCsv.Nullable(f[12]), ParentLevel5Name = SeedCsv.Nullable(f[13]),
+ };
+
+ private static FundHierarchy FundFrom(string[] f) => new()
+ {
+ Code = f[0],
+ Description = SeedCsv.Nullable(f[1]),
+ ParentLevel0Code = SeedCsv.Nullable(f[2]), ParentLevel0Name = SeedCsv.Nullable(f[3]),
+ ParentLevel1Code = SeedCsv.Nullable(f[4]), ParentLevel1Name = SeedCsv.Nullable(f[5]),
+ ParentLevel2Code = SeedCsv.Nullable(f[6]), ParentLevel2Name = SeedCsv.Nullable(f[7]),
+ ParentLevel3Code = SeedCsv.Nullable(f[8]), ParentLevel3Name = SeedCsv.Nullable(f[9]),
+ ParentLevel4Code = SeedCsv.Nullable(f[10]), ParentLevel4Name = SeedCsv.Nullable(f[11]),
+ ParentLevel5Code = SeedCsv.Nullable(f[12]), ParentLevel5Name = SeedCsv.Nullable(f[13]),
+ };
+
+ private static ActivityHierarchy ActivityFrom(string[] f) => new()
+ {
+ Code = f[0],
+ Description = SeedCsv.Nullable(f[1]),
+ ParentLevel0Code = SeedCsv.Nullable(f[2]), ParentLevel0Name = SeedCsv.Nullable(f[3]),
+ ParentLevel1Code = SeedCsv.Nullable(f[4]), ParentLevel1Name = SeedCsv.Nullable(f[5]),
+ ParentLevel2Code = SeedCsv.Nullable(f[6]), ParentLevel2Name = SeedCsv.Nullable(f[7]),
+ ParentLevel3Code = SeedCsv.Nullable(f[8]), ParentLevel3Name = SeedCsv.Nullable(f[9]),
+ ParentLevel4Code = SeedCsv.Nullable(f[10]), ParentLevel4Name = SeedCsv.Nullable(f[11]),
+ ParentLevel5Code = SeedCsv.Nullable(f[12]), ParentLevel5Name = SeedCsv.Nullable(f[13]),
+ };
+
+ // DepartmentSource.csv columns:
+ // 0 D code, 1 D name, 2 E code, 3 E name, 4 F code, 5 F name, 6 G code, 7 G name, ... (fact columns ignored)
+ private static List DepartmentRows() =>
+ SeedCsv.ReadRows("DepartmentSource.csv", f => f)
+ .GroupBy(f => f[6])
+ .Select(rows => rows.First())
+ .Select(f => new DepartmentHierarchy
+ {
+ Code = f[6],
+ Description = SeedCsv.Nullable(f[7]),
+ ParentLevelACode = "UCD", ParentLevelAName = "UC Davis",
+ ParentLevelBCode = "CAES", ParentLevelBName = "College of Agricultural and Environmental Sciences",
+ ParentLevelCCode = "AAES", ParentLevelCName = "Agricultural Experiment Station",
+ ParentLevelDCode = SeedCsv.Nullable(f[0]), ParentLevelDName = SeedCsv.Nullable(f[1]),
+ ParentLevelECode = SeedCsv.Nullable(f[2]), ParentLevelEName = SeedCsv.Nullable(f[3]),
+ ParentLevelFCode = SeedCsv.Nullable(f[4]), ParentLevelFName = SeedCsv.Nullable(f[5]),
+ ParentLevelGCode = SeedCsv.Nullable(f[6]), ParentLevelGName = SeedCsv.Nullable(f[7]),
+ })
+ .ToList();
+}
diff --git a/server.core/Data/Seed/AccountHierarchy.csv b/server.core/Data/Seed/AccountHierarchy.csv
new file mode 100644
index 0000000..97a0afa
--- /dev/null
+++ b/server.core/Data/Seed/AccountHierarchy.csv
@@ -0,0 +1,183 @@
+Code,Description,ParentLevel0Code,ParentLevel0Name,ParentLevel1Code,ParentLevel1Name,ParentLevel2Code,ParentLevel2Name,ParentLevel3Code,ParentLevel3Name,ParentLevel4Code,ParentLevel4Name,ParentLevel5Code,ParentLevel5Name,LoadedAt
+400900,Self Supporting Degree Program Fees,4XXXXX,All Revenue,40000A,Student Tuition and Fees Net,40000B,Tuition gross,40090C,Self Supporting Degree Fee,40090D,Self Supporting Degree Fee Revenue,400900,Self Supporting Degree Program Fees,2026-07-01 13:38:24.871
+402600,Course Material and Service Fees Undergraduate,4XXXXX,All Revenue,40000A,Student Tuition and Fees Net,40200B,Fees Gross,40260C,Course materials and service fees CMSF,40260D,Course materials and service fees,402600,Course Material and Service Fees Undergraduate,2026-07-01 13:38:24.871
+405200,Federal Contracts Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40500B,Federal Contracts and Grants excl Financial Aid,40520C,Federal Contracts,40520D,Federal Contract Revenues,405200,Federal Contracts Revenue,2026-07-01 13:38:24.871
+405400,Federal Grants Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40500B,Federal Contracts and Grants excl Financial Aid,40540C,Federal Grants,40540D,Federal Grants Revenues,405400,Federal Grants Revenue,2026-07-01 13:38:24.871
+406000,Special State Appropriations Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40600B,State Contracts and Grants Revenue,40600C,Special State Appropriations,40600D,Special State Appropriations Revenues,406000,Special State Appropriations Revenue,2026-07-01 13:38:24.871
+406300,State Grants Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40600B,State Contracts and Grants Revenue,40630C,State Grants,40630D,State Grants Revenues,406300,State Grants Revenue,2026-07-01 13:38:24.871
+408000,Private Contracts Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40800B,Private Contracts and Grants Revenue,40800C,Private Contracts,40800D,Private Contracts Revenues,408000,Private Contracts Revenue,2026-07-01 13:38:24.871
+408200,Private Grants Revenue,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40800B,Private Contracts and Grants Revenue,40820C,Private Grants,40820D,Private Grants Revenues,408200,Private Grants Revenue,2026-07-01 13:38:24.871
+410000,External Sales and Services Revenues,4XXXXX,All Revenue,41000A,Sales and Services Educational,41000B,Sales and Services of Educational Activities Net,41000C,Sales and Services of Educational Activities Revenues,41000D,Sales and Services of Educational Activities Revenue,410000,External Sales and Services Revenues,2026-07-01 13:38:24.871
+410004,Conferences Workshop Symposium and Seminars Revenue,4XXXXX,All Revenue,41000A,Sales and Services Educational,41000B,Sales and Services of Educational Activities Net,41000C,Sales and Services of Educational Activities Revenues,41000D,Sales and Services of Educational Activities Revenue,410004,Conferences Workshop Symposium and Seminars Revenue,2026-07-01 13:38:24.871
+440001,Agency Fund Revenue,4XXXXX,All Revenue,44000A,Other Operating Revenue Net,44000B,Other Operating Revenues Net,44000C,Other Operating Revenue,44000D,Other Operating Revenues,440001,Agency Fund Revenue,2026-07-01 13:38:24.871
+440009,Other Miscellaneous Income,4XXXXX,All Revenue,44000A,Other Operating Revenue Net,44000B,Other Operating Revenues Net,44000C,Other Operating Revenue,44000D,Other Operating Revenues,440009,Other Miscellaneous Income,2026-07-01 13:38:24.871
+469000,UCDF Gift Revenue Cash,4XXXXX,All Revenue,46900A,Foundation Operating Revenue Foundation Use Only,46900B,Foundation Operating Revenue UCD Foundation Only,46900C,Foundation Operating Revenues UCD Foundation Only,46900D,UCD Foundation Operating Revenues,469000,UCDF Gift Revenue Cash,2026-07-01 13:38:24.871
+480000,Current Use Gift Revenue,4XXXXX,All Revenue,48000A,Private Gifts NonCapital,48000B,Private Gifts,48000C,Private Gift Revenue NonCapital,48000D,Private Gift Revenues,480000,Current Use Gift Revenue,2026-07-01 13:38:24.871
+480007,Gift Fees,4XXXXX,All Revenue,48000A,Private Gifts NonCapital,48000B,Private Gifts,48000C,Private Gift Revenue NonCapital,48000D,Private Gift Revenues,480007,Gift Fees,2026-07-01 13:38:24.871
+480021,Foundation Endowment and FFE Income,4XXXXX,All Revenue,48000A,Private Gifts NonCapital,48000B,Private Gifts,48000C,Private Gift Revenue NonCapital,48000D,Private Gift Revenues,480021,Foundation Endowment and FFE Income,2026-07-01 13:38:24.871
+480022,Regents Endowment and FFE Income,4XXXXX,All Revenue,48000A,Private Gifts NonCapital,48000B,Private Gifts,48000C,Private Gift Revenue NonCapital,48000D,Private Gift Revenues,480022,Regents Endowment and FFE Income,2026-07-01 13:38:24.871
+480025,Endowment Infrastructure fee,4XXXXX,All Revenue,48000A,Private Gifts NonCapital,48000B,Private Gifts,48000C,Private Gift Revenue NonCapital,48000D,Private Gift Revenues,480025,Endowment Infrastructure fee,2026-07-01 13:38:24.871
+500000,Faculty Salaries Ladder Rank and Equivalent,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500000,Faculty Salaries Ladder Rank and Equivalent,2026-07-01 13:38:24.871
+500001,Faculty Salaries Lecturers Continuing,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500001,Faculty Salaries Lecturers Continuing,2026-07-01 13:38:24.871
+500002,Faculty Salaries Lecturers Temporary,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500002,Faculty Salaries Lecturers Temporary,2026-07-01 13:38:24.871
+500003,Faculty Salaries Clinical InResident Adjunct,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500003,Faculty Salaries Clinical InResident Adjunct,2026-07-01 13:38:24.871
+500010,Teaching Assistant Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500010,Teaching Assistant Salaries and Wages,2026-07-01 13:38:24.871
+500011,Associate Instructors Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500011,Associate Instructors Salaries and Wages,2026-07-01 13:38:24.871
+500035,Academic Coordinators and Admin Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500035,Academic Coordinators and Admin Salaries and Wages,2026-07-01 13:38:24.871
+500090,Agronomists and Other NonFaculty Scientists Ladder Rank,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500090,Agronomists and Other NonFaculty Scientists Ladder Rank,2026-07-01 13:38:24.871
+500092,Graduate Student Researchers Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500092,Graduate Student Researchers Salaries and Wages,2026-07-01 13:38:24.871
+500093,PostDoctorates and Assistant Researchers,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500093,PostDoctorates and Assistant Researchers,2026-07-01 13:38:24.871
+500094,Readers,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500094,Readers,2026-07-01 13:38:24.871
+500098,Other Academic Salaries and Wages Expense,5XXXXX,All Expenses,50000A,Salaries and Wages,50000B,Academic Salaries and Wages,50000C,Academic Salaries and Wages Expense,50000D,Academic Salaries and Wages Expenses,500098,Other Academic Salaries and Wages Expense,2026-07-01 13:38:24.871
+501000,Staff Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50100B,NonAcademic Salaries and Wages,50100C,NonAcademic Salaries and Wages Expense,50100D,Staff Salaries and Wages Expenses,501000,Staff Salaries and Wages,2026-07-01 13:38:24.871
+501001,Contract Limited Floater and Per Diem Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50100B,NonAcademic Salaries and Wages,50100C,NonAcademic Salaries and Wages Expense,50100D,Staff Salaries and Wages Expenses,501001,Contract Limited Floater and Per Diem Salaries and Wages,2026-07-01 13:38:24.871
+501002,Student Employee Salaries and Wages,5XXXXX,All Expenses,50000A,Salaries and Wages,50100B,NonAcademic Salaries and Wages,50100C,NonAcademic Salaries and Wages Expense,50100D,Staff Salaries and Wages Expenses,501002,Student Employee Salaries and Wages,2026-07-01 13:38:24.871
+501090,Salary and Wages Default,5XXXXX,All Expenses,50000A,Salaries and Wages,50100B,NonAcademic Salaries and Wages,50100C,NonAcademic Salaries and Wages Expense,50100D,Staff Salaries and Wages Expenses,501090,Salary and Wages Default,2026-07-01 13:38:24.871
+501093,Staff Recognition and Development,5XXXXX,All Expenses,50000A,Salaries and Wages,50100B,NonAcademic Salaries and Wages,50100C,NonAcademic Salaries and Wages Expense,50100D,Staff Salaries and Wages Expenses,501093,Staff Recognition and Development,2026-07-01 13:38:24.871
+505001,CBR Addon Prop 2,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,505001,CBR Addon Prop 2,2026-07-01 13:38:24.871
+505500,UCRP Supplemental Assessment Benefits Expense Interest,5XXXXX,All Expenses,50500A,Pension Benefits,50500B,UCRP,50550C,UCRP Supplemental Assessment Interest Exp,50550D,UCRP Supplemental Assessment Interest,505500,UCRP Supplemental Assessment Benefits Expense Interest,2026-07-01 13:38:24.871
+507000,Graduate Student Health Remission,5XXXXX,All Expenses,50700A,Other Employee Benefits,50700B,Health and Welfare Benefits,50700C,Group Health Insurance,50700D,Group Health Insurance Plans,507000,Graduate Student Health Remission,2026-07-01 13:38:24.871
+508000,Composite Benefit Rate Campus Payment CBR Expense,5XXXXX,All Expenses,50700A,Other Employee Benefits,50800B,Composite Benefit Rate Campus Payment,50800C,Composite Benefit Rate Campus Payments,50800D,Composite Benefit Rate Campus Payment Expense,508000,Composite Benefit Rate Campus Payment CBR Expense,2026-07-01 13:38:24.871
+508100,Relocation Benefits Expense,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508100,Relocation Benefits Expense,2026-07-01 13:38:24.871
+508120,Graduate Student Fee Remission,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508120,Graduate Student Fee Remission,2026-07-01 13:38:24.871
+508130,Nonresident Fee Remission,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508130,Nonresident Fee Remission,2026-07-01 13:38:24.871
+508132,2nd3r Year NRST Remission,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508132,2nd3r Year NRST Remission,2026-07-01 13:38:24.871
+508139,Graduate Student Campus Rebate,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508139,Graduate Student Campus Rebate,2026-07-01 13:38:24.871
+508199,Other Local and Campus Specific Benefit Programs,5XXXXX,All Expenses,50700A,Other Employee Benefits,50810B,Local Campus Specific Benefit Programs,50810C,Campus Specific Local Benefit Programs,50810D,Local Campus Specific Benefit programs D,508199,Other Local and Campus Specific Benefit Programs,2026-07-01 13:38:24.871
+508300,Vacation Accrued Expenses,5XXXXX,All Expenses,50700A,Other Employee Benefits,50820B,Benefits Accrual,50830C,Vacation Accrual Expense,50830D,Vacation Accruals,508300,Vacation Accrued Expenses,2026-07-01 13:38:24.871
+508301,Vacation and Leave Usage Salaries and Wages,5XXXXX,All Expenses,50700A,Other Employee Benefits,50820B,Benefits Accrual,50830C,Vacation Accrual Expense,50830D,Vacation Accruals,508301,Vacation and Leave Usage Salaries and Wages,2026-07-01 13:38:24.871
+510007,Undergrad Scholarship and Fellowship Expense,5XXXXX,All Expenses,51000A,Financial Aid and Scholarships,51000B,Undergraduate Aid and Scholarships Expense,51000C,Undergraduate Financial Aid and Scholarships Expense,51000D,Undergraduate Financial Aid and Scholarship Expense - Gross,510007,Undergrad Scholarship and Fellowship Expense,2026-07-01 13:38:24.871
+511000,Graduate Fellowship Tuition and Fees,5XXXXX,All Expenses,51000A,Financial Aid and Scholarships,51100B,Graduate and Professional Aid and Fellowship,51100C,Graduate Financial Aid and Fellowship Expenses,51100D,Graduate Academic and Professional Aid and Fellowship Expense Gross,511000,Graduate Fellowship Tuition and Fees,2026-07-01 13:38:24.871
+511001,Graduate Fellowship Nonresident,5XXXXX,All Expenses,51000A,Financial Aid and Scholarships,51100B,Graduate and Professional Aid and Fellowship,51100C,Graduate Financial Aid and Fellowship Expenses,51100D,Graduate Academic and Professional Aid and Fellowship Expense Gross,511001,Graduate Fellowship Nonresident,2026-07-01 13:38:24.871
+511002,Graduate Fellowship Stipend,5XXXXX,All Expenses,51000A,Financial Aid and Scholarships,51100B,Graduate and Professional Aid and Fellowship,51100C,Graduate Financial Aid and Fellowship Expenses,51100D,Graduate Academic and Professional Aid and Fellowship Expense Gross,511002,Graduate Fellowship Stipend,2026-07-01 13:38:24.871
+520350,Pharmaceuticals Drugs Expense,5XXXXX,All Expenses,52000A,Supplies and Services,52000B,Health Supplies and Pharmaceuticals,52035C,Pharmaceuticals,52035D,Pharmaceutical Drugs,520350,Pharmaceuticals Drugs Expense,2026-07-01 13:38:24.871
+520506,Bandages and Supports,5XXXXX,All Expenses,52000A,Supplies and Services,52000B,Health Supplies and Pharmaceuticals,52050C,Other Medical Care Materials and Supplies,52050D,Other Medical Care Materials and Supplies Expense,520506,Bandages and Supports,2026-07-01 13:38:24.871
+520529,Other Medical Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52000B,Health Supplies and Pharmaceuticals,52050C,Other Medical Care Materials and Supplies,52050D,Other Medical Care Materials and Supplies Expense,520529,Other Medical Supplies,2026-07-01 13:38:24.871
+520600,Instruments and Minor Medical Equipment Expenses,5XXXXX,All Expenses,52000A,Supplies and Services,52000B,Health Supplies and Pharmaceuticals,52060C,Instruments and Minor Medical Equipment,52060D,Instruments and Minor Medical Equipment Expense,520600,Instruments and Minor Medical Equipment Expenses,2026-07-01 13:38:24.871
+522000,Food Beverage,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52200C,Food and Beverage Supplies,52200D,Food and Beverage Dining Services Supplies,522000,Food Beverage,2026-07-01 13:38:24.871
+522001,Food Equipment and Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52200C,Food and Beverage Supplies,52200D,Food and Beverage Dining Services Supplies,522001,Food Equipment and Supplies,2026-07-01 13:38:24.871
+522200,Postage and Mail Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52220C,Office and Administrative Supplies,52220D,Office and Administrative Supplies Expense,522200,Postage and Mail Supplies,2026-07-01 13:38:24.871
+522201,General Office Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52220C,Office and Administrative Supplies,52220D,Office and Administrative Supplies Expense,522201,General Office Supplies,2026-07-01 13:38:24.871
+522300,Furniture PPM Expense,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52230C,Physical Plant Manufacturing and Furnishing Materials C,52230D,Physical Plant Manufacturing and Furnishing Materials D,522300,Furniture PPM Expense,2026-07-01 13:38:24.871
+522400,Lab Supplies and Accessories,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522400,Lab Supplies and Accessories,2026-07-01 13:38:24.871
+522401,Lab Chemicals and Gases,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522401,Lab Chemicals and Gases,2026-07-01 13:38:24.871
+522402,Lab Animal Purchases,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522402,Lab Animal Purchases,2026-07-01 13:38:24.871
+522404,Lab Animal Bedding,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522404,Lab Animal Bedding,2026-07-01 13:38:24.871
+522406,Lab Animal Feed,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522406,Lab Animal Feed,2026-07-01 13:38:24.871
+522407,Live Plant Accessories and Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52240C,Laboratory Supplies,52240D,Laboratory Supplies and Equipment Noncapital,522407,Live Plant Accessories and Supplies,2026-07-01 13:38:24.871
+522511,Books and Publications,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52250C,Printed Items and Other Classroom Teaching Aides,52250D,Printed Items Course Materials and Other Classroom Teaching Aides,522511,Books and Publications,2026-07-01 13:38:24.871
+522513,Library Materials,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52250C,Printed Items and Other Classroom Teaching Aides,52250D,Printed Items Course Materials and Other Classroom Teaching Aides,522513,Library Materials,2026-07-01 13:38:24.871
+522515,Classroom Materials,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52250C,Printed Items and Other Classroom Teaching Aides,52250D,Printed Items Course Materials and Other Classroom Teaching Aides,522515,Classroom Materials,2026-07-01 13:38:24.871
+522600,Subscriptions and Memberships Fed Allowable,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52260C,Subscriptions and Memberships Federally Chargeable,52260D,Subscriptions and Memberships Federally Chargeable Expense,522600,Subscriptions and Memberships Fed Allowable,2026-07-01 13:38:24.871
+522800,Employee Wearing Apparel,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52280C,Employee Wearing Apparel Exp,52280D,Employee Wearing Apparel Expense,522800,Employee Wearing Apparel,2026-07-01 13:38:24.871
+523100,Medical Center Nonmedical Equipment,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52310C,Other Minor Equipment,52310D,Other Minor Equipment Expense,523100,Medical Center Nonmedical Equipment,2026-07-01 13:38:24.871
+523210,BUILDING and GROUNDS MAINTENANCE,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52320C,MC Other Supplies,52320D,Other Non Medical Supplies Expense,523210,BUILDING and GROUNDS MAINTENANCE,2026-07-01 13:38:24.871
+523221,Other Non Medical Supplies,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52320C,MC Other Supplies,52320D,Other Non Medical Supplies Expense,523221,Other Non Medical Supplies,2026-07-01 13:38:24.871
+523222,Law Enforcement Supplies and Explosives,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52320C,MC Other Supplies,52320D,Other Non Medical Supplies Expense,523222,Law Enforcement Supplies and Explosives,2026-07-01 13:38:24.871
+523223,Reimbursement of Supplies and Materials,5XXXXX,All Expenses,52000A,Supplies and Services,52200B,Supplies Non Health,52320C,MC Other Supplies,52320D,Other Non Medical Supplies Expense,523223,Reimbursement of Supplies and Materials,2026-07-01 13:38:24.871
+525900,Capital Expense Equipment,5XXXXX,All Expenses,52000A,Supplies and Services,52500B,Purchases to be Capitalized,52590C,Capital Expense Equipment C,52590D,Capital Expenditure Equipment,525900,Capital Expense Equipment,2026-07-01 13:38:24.871
+530101,Consulting Services Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53010C,Consultants and Other Temporarily Contracted Staff,53010D,Consultants and Other Temporarily Contract Staff,530101,Consulting Services Expense,2026-07-01 13:38:24.871
+530102,Outside Purchased Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53010C,Consultants and Other Temporarily Contracted Staff,53010D,Consultants and Other Temporarily Contract Staff,530102,Outside Purchased Services,2026-07-01 13:38:24.871
+530103,Research Subject Payments,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53010C,Consultants and Other Temporarily Contracted Staff,53010D,Consultants and Other Temporarily Contract Staff,530103,Research Subject Payments,2026-07-01 13:38:24.871
+530104,Reimbursement of Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53010C,Consultants and Other Temporarily Contracted Staff,53010D,Consultants and Other Temporarily Contract Staff,530104,Reimbursement of Services,2026-07-01 13:38:24.871
+530400,Promotional Advertising,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53040C,Advertising Federally Unchargeable,53040D,Advertising Federally Unchargeable D,530400,Promotional Advertising,2026-07-01 13:38:24.871
+530500,Non Promotional Advertising,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53050C,Advertising Federally Chargeable,53050D,Advertising Federally Chargeable D,530500,Non Promotional Advertising,2026-07-01 13:38:24.871
+530601,Credit Card Fees,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53060C,Banking Investment Manager Fees,53060D,Banking and Investment Manager Fees,530601,Credit Card Fees,2026-07-01 13:38:24.871
+530701,Regulatory Fees Permits and Licenses,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53070C,Legal Services,53070D,Legal Services D,530701,Regulatory Fees Permits and Licenses,2026-07-01 13:38:24.871
+531010,General Liability Insurance,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53090C,Insurance and Licenses,53100D,Insurance Other,531010,General Liability Insurance,2026-07-01 13:38:24.871
+531100,License and Taxes Expense excluding Income,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53090C,Insurance and Licenses,53110D,Licenses and Taxes Other than on Income,531100,License and Taxes Expense excluding Income,2026-07-01 13:38:24.871
+532200,Equipment Leases or Rentals,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53220C,Leases and Rentals Equipment,53220D,Leases and Rentals Expense Equipment,532200,Equipment Leases or Rentals,2026-07-01 13:38:24.871
+532301,Vehicle and Fleet Supplies and Equipment,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532301,Vehicle and Fleet Supplies and Equipment,2026-07-01 13:38:24.871
+532302,Repairs and Maintenance Expenses,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532302,Repairs and Maintenance Expenses,2026-07-01 13:38:24.871
+532303,Construction Supplies and Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532303,Construction Supplies and Services,2026-07-01 13:38:24.871
+532304,Other Materials or Supplies,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532304,Other Materials or Supplies,2026-07-01 13:38:24.871
+532309,Landscaping Supplies,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532309,Landscaping Supplies,2026-07-01 13:38:24.871
+532310,Cleaning Supplies and Minor Equipment,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532310,Cleaning Supplies and Minor Equipment,2026-07-01 13:38:24.871
+532313,Laundry Service,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532313,Laundry Service,2026-07-01 13:38:24.871
+532315,Facility Moving Storage Expenses,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532315,Facility Moving Storage Expenses,2026-07-01 13:38:24.871
+532316,Facilities and Maintenance Tools and Equipment,5XXXXX,All Expenses,53000A,Other Operating Expense,53200B,Facilities Construction Leasing and Maintenance,53230C,Repairs Maintenance,53230D,Repairs and Maintenance Expense,532316,Facilities and Maintenance Tools and Equipment,2026-07-01 13:38:24.871
+533000,First 25K of Subcontract Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53300B,Subcontract Services,53300C,First 25K of Subcontract Services C,53300D,First 25K of Subcontract Svc Expense,533000,First 25K of Subcontract Services,2026-07-01 13:38:24.871
+533100,Expense In Excess of 25K On Subcontract Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53300B,Subcontract Services,53310C,Expense In Excess of 25K On Subcontract Services C,53310D,Expense In Excess of 25K On Subcontract Svc,533100,Expense In Excess of 25K On Subcontract Services,2026-07-01 13:38:24.871
+534001,Software License Maintenance,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53400C,Software Maintenance and Licenses,53400D,Software Maintenance and Licenses Expense,534001,Software License Maintenance,2026-07-01 13:38:24.871
+534010,SBITA expense fixed and variable if included in PV calc,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53400C,Software Maintenance and Licenses,53401D,SBITA expense fixed and variable if included in PV calc D,534010,SBITA expense fixed and variable if included in PV calc,2026-07-01 13:38:24.871
+534100,Computer Network Equipment and Supplies,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53410C,Computers and Network Infrastructure,53410D,Computers and Network Infrastructure Expense,534100,Computer Network Equipment and Supplies,2026-07-01 13:38:24.871
+534101,Software Purchases,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53410C,Computers and Network Infrastructure,53410D,Computers and Network Infrastructure Expense,534101,Software Purchases,2026-07-01 13:38:24.871
+534200,Telecommunications Expense and Accessories,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53420C,Telecommunications,53420D,Telecommunications Expense,534200,Telecommunications Expense and Accessories,2026-07-01 13:38:24.871
+534309,Other IT Accessories and Peripherals,5XXXXX,All Expenses,53000A,Other Operating Expense,53400B,IT and Communications Services,53430C,IT Accessories and Peripherals,53430D,IT Accessories and Peripherals Expense,534309,Other IT Accessories and Peripherals,2026-07-01 13:38:24.871
+535100,External Laboratory Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53500B,Medical Professional Fees,53510C,Purchased Medical Services C,53510D,Purchased Medical Services,535100,External Laboratory Services,2026-07-01 13:38:24.871
+535101,Transcription Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53500B,Medical Professional Fees,53510C,Purchased Medical Services C,53510D,Purchased Medical Services,535101,Transcription Services,2026-07-01 13:38:24.871
+536001,In State Airfare,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536001,In State Airfare,2026-07-01 13:38:24.871
+536002,In State Lodging,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536002,In State Lodging,2026-07-01 13:38:24.871
+536003,In State Transportation,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536003,In State Transportation,2026-07-01 13:38:24.871
+536004,In State Meals Incidentals,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536004,In State Meals Incidentals,2026-07-01 13:38:24.871
+536005,In State Travel Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536005,In State Travel Expense,2026-07-01 13:38:24.871
+536011,Out of State Airfare,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536011,Out of State Airfare,2026-07-01 13:38:24.871
+536012,Out of State Lodging,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536012,Out of State Lodging,2026-07-01 13:38:24.871
+536013,Out of State Transportation,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536013,Out of State Transportation,2026-07-01 13:38:24.871
+536014,Out of State Meals Incidentals,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536014,Out of State Meals Incidentals,2026-07-01 13:38:24.871
+536015,Out of State Travel Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536015,Out of State Travel Expense,2026-07-01 13:38:24.871
+536021,International Airfare,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536021,International Airfare,2026-07-01 13:38:24.871
+536022,International Lodging,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536022,International Lodging,2026-07-01 13:38:24.871
+536023,International Transportation,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536023,International Transportation,2026-07-01 13:38:24.871
+536024,International Meals Incidentals,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536024,International Meals Incidentals,2026-07-01 13:38:24.871
+536025,International Travel Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536025,International Travel Expense,2026-07-01 13:38:24.871
+536093,Travel Reimbursement,5XXXXX,All Expenses,53000A,Other Operating Expense,53600B,Travel and Transit,53600C,Travel and Transit Expense,53600D,Travel and Transit Expenses,536093,Travel Reimbursement,2026-07-01 13:38:24.871
+536200,Employee Noncash Awards and Other Gifts,5XXXXX,All Expenses,53000A,Other Operating Expense,53620B,Entertainment and Gifts,53620C,Employee Noncash Awards and Other Gifts B,53620D,Employee Noncash Awards and Other Gifts C,536200,Employee Noncash Awards and Other Gifts,2026-07-01 13:38:24.871
+536201,Gift Cards,5XXXXX,All Expenses,53000A,Other Operating Expense,53620B,Entertainment and Gifts,53620C,Employee Noncash Awards and Other Gifts B,53620D,Employee Noncash Awards and Other Gifts C,536201,Gift Cards,2026-07-01 13:38:24.871
+536300,NonEmployee Gifts,5XXXXX,All Expenses,53000A,Other Operating Expense,53620B,Entertainment and Gifts,53630C,NonEmployee Gifts C,53630D,NonEmployee Gifts D,536300,NonEmployee Gifts,2026-07-01 13:38:24.871
+536400,Entertainment Expenses,5XXXXX,All Expenses,53000A,Other Operating Expense,53620B,Entertainment and Gifts,53640C,Social and Entertainment Expenses,53640D,Social and Entertainment Expense,536400,Entertainment Expenses,2026-07-01 13:38:24.871
+536401,Entertainment Alcoholic Beverages,5XXXXX,All Expenses,53000A,Other Operating Expense,53620B,Entertainment and Gifts,53640C,Social and Entertainment Expenses,53640D,Social and Entertainment Expense,536401,Entertainment Alcoholic Beverages,2026-07-01 13:38:24.871
+536500,Business Meeting Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53650B,Meetings and Training,53650C,Business Meetings Expense,53650D,Business Meeting Expenses,536500,Business Meeting Expense,2026-07-01 13:38:24.871
+536600,Honorarium Payments and Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53650B,Meetings and Training,53660C,Honorarium Payments,53660D,Honorariums Expense,536600,Honorarium Payments and Expense,2026-07-01 13:38:24.871
+536601,Stipends Non Student,5XXXXX,All Expenses,53000A,Other Operating Expense,53650B,Meetings and Training,53660C,Honorarium Payments,53660D,Honorariums Expense,536601,Stipends Non Student,2026-07-01 13:38:24.871
+536700,Conference and Registration Fees,5XXXXX,All Expenses,53000A,Other Operating Expense,53650B,Meetings and Training,53670C,Conference Training,53670D,Conference and Training Expense,536700,Conference and Registration Fees,2026-07-01 13:38:24.871
+538000,Facilities and Administration Costs FA,5XXXXX,All Expenses,53000A,Other Operating Expense,53800B,Facilities and Administration Cost B,53800C,Facilities and Administration Cost C,53800D,FA Expense,538000,Facilities and Administration Costs FA,2026-07-01 13:38:24.871
+538100,Facilities and Administration FA Offset Recovery,5XXXXX,All Expenses,53000A,Other Operating Expense,53800B,Facilities and Administration Cost B,53810C,FA Offset Recovery C,53810D,FA Offset Recovery,538100,Facilities and Administration FA Offset Recovery,2026-07-01 13:38:24.871
+539000,Printing of Forms,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,53900C,Printing and Reproduction Services C,53900D,Printing and Reproduction Services,539000,Printing of Forms,2026-07-01 13:38:24.871
+539002,Printing Photography and Other Materials,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,53900C,Printing and Reproduction Services C,53900D,Printing and Reproduction Services,539002,Printing Photography and Other Materials,2026-07-01 13:38:24.871
+539100,Freight and Shipping Services,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,53910C,Freight,53910D,Freight Expense,539100,Freight and Shipping Services,2026-07-01 13:38:24.871
+539300,Bad Debt Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,53930C,Bad Debt Other,53930D,Bad Debt Expense Other,539300,Bad Debt Expense,2026-07-01 13:38:24.871
+539999,Other Miscellaneous and Other Operating Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,53950C,Other Operating Exp,53950D,Other Operating Expenses,539999,Other Miscellaneous and Other Operating Expense,2026-07-01 13:38:24.871
+724020,Subcontract Revenue Awarded from UC San Francisco,4XXXXX,All Revenue,40500A,Contracts and Grants Noncapital,40840B,Intercampus Subcontract Revenue,72400C,Intercampus Subcontract Revenues C,72400D,Intercampus Subcontract Revenues D,724020,Subcontract Revenue Awarded from UC San Francisco,2026-07-01 13:38:24.871
+725120,Subcontract Expenses Awarded to ANR,5XXXXX,All Expenses,53000A,Other Operating Expense,53300B,Subcontract Services,72500C,Intercampus Subcontract Expenses C,72500D,Intercampus Subcontract Expenses D,725120,Subcontract Expenses Awarded to ANR,2026-07-01 13:38:24.871
+734000,UCDMC Recharges Other Supplies and Purchased Services,5XXXXX,All Expenses,73000A,Medical Center Recharge Sales and Services,73000B,Medical Center Recharges B,73000C,Medical Center Recharges C,73000D,Medical Center Recharges D,734000,UCDMC Recharges Other Supplies and Purchased Services,2026-07-01 13:38:24.871
+741000,STIP Investment Distribution,4XXXXX,All Revenue,48200A,Income on Investments,48200B,Short Term Investment Pool STIP,74100C,STIP Distribution,74100D,STIP Investment Distribution D,741000,STIP Investment Distribution,2026-07-01 13:38:24.871
+744000,GEP Annual Payout Income,4XXXXX,All Revenue,48200A,Income on Investments,48230B,General Endowment Pool GEP,74400C,GEP Payout,74400D,GEP Payout Income,744000,GEP Annual Payout Income,2026-07-01 13:38:24.871
+750200,UCOP Insurance Appropriations BSASGAEL Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53000B,Business Legal and Insurance Services,53090C,Insurance and Licenses,75020D,UCOP Insurance Appropriations BSASGAEL,750200,UCOP Insurance Appropriations BSASGAEL Expense,2026-07-01 13:38:24.871
+750500,Other UCOP Expense Allocations Operating Expense,5XXXXX,All Expenses,53000A,Other Operating Expense,53900B,Other Operating Expenses B,75050C,Other UCOP Expense Allocations Operating,75050D,Other Operating UCOP Expense Allocations,750500,Other UCOP Expense Allocations Operating Expense,2026-07-01 13:38:24.871
+770000,Facilities and Equipment Maintenance Services,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770000,Facilities and Equipment Maintenance Services,2026-07-01 13:38:24.871
+770001,Space and Event Services,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770001,Space and Event Services,2026-07-01 13:38:24.871
+770002,Materials and Supplies Recharges,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770002,Materials and Supplies Recharges,2026-07-01 13:38:24.871
+770003,Research or Lab Support Services,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770003,Research or Lab Support Services,2026-07-01 13:38:24.871
+770004,Transportation Recharges,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770004,Transportation Recharges,2026-07-01 13:38:24.871
+770005,IT Services,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770005,IT Services,2026-07-01 13:38:24.871
+770006,Other Support Services,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770006,Other Support Services,2026-07-01 13:38:24.871
+770007,DCA Recharge Expense,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770007,DCA Recharge Expense,2026-07-01 13:38:24.871
+770009,Central Campus Income Assessment Expense,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770009,Central Campus Income Assessment Expense,2026-07-01 13:38:24.871
+770010,Departmental Assessment Expense,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770010,Departmental Assessment Expense,2026-07-01 13:38:24.871
+770012,EPIC Study Recharges,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770012,EPIC Study Recharges,2026-07-01 13:38:24.871
+770017,MC PURCHASED SERVICES RECHARGES EXP,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77000D,Internal Recharge Expense D,770017,MC PURCHASED SERVICES RECHARGES EXP,2026-07-01 13:38:24.871
+770101,Balances to Reserve Expense,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77010D,Fund Transfer Expense D,770101,Balances to Reserve Expense,2026-07-01 13:38:24.871
+770103,Fund Transfer Expense,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,77000C,Internal Recharge Expense,77010D,Fund Transfer Expense D,770103,Fund Transfer Expense,2026-07-01 13:38:24.871
+775000,UCD Recharge Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77500D,Internal Recharge Revenue,775000,UCD Recharge Revenue,2026-07-01 13:38:24.871
+775002,DCA Recharge Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77500D,Internal Recharge Revenue,775002,DCA Recharge Revenue,2026-07-01 13:38:24.871
+775004,Central Campus Income Assessment Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77500D,Internal Recharge Revenue,775004,Central Campus Income Assessment Revenue,2026-07-01 13:38:24.871
+775005,Departmental Assessment Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77500D,Internal Recharge Revenue,775005,Departmental Assessment Revenue,2026-07-01 13:38:24.871
+775101,Balances to Reserve Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77510D,Fund Transfers Revenue D,775101,Balances to Reserve Revenue,2026-07-01 13:38:24.871
+775103,Fund Transfer Revenue,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,77500C,Internal Recharge Revenues,77510D,Fund Transfers Revenue D,775103,Fund Transfer Revenue,2026-07-01 13:38:24.871
+775B00,Central Campus Appropriations Ongoing,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77501D,Central Campus Appropriations,775B00,Central Campus Appropriations Ongoing,2026-07-01 13:38:24.871
+775B05,VC Dean Office Appropriations Ongoing,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77502D,Unit Level Appropriation Transfers,775B05,VC Dean Office Appropriations Ongoing,2026-07-01 13:38:24.871
+775B06,Appropriations Transfer Between Units Ongoing,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77502D,Unit Level Appropriation Transfers,775B06,Appropriations Transfer Between Units Ongoing,2026-07-01 13:38:24.871
+775B10,Central Campus Appropriations One Time,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77501D,Central Campus Appropriations,775B10,Central Campus Appropriations One Time,2026-07-01 13:38:24.871
+775B14,VC Dean Office Appropriations One Time,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77502D,Unit Level Appropriation Transfers,775B14,VC Dean Office Appropriations One Time,2026-07-01 13:38:24.871
+775B15,Appropriations Transfer Btwn Units One Time,4XXXXX,All Revenue,74000A,Internal Revenue Transfer and Appropriations,74000B,Internal Revenue Transfer and Appropriations B,74000C,Internal Revenue Transfer and Appropriations C,77502D,Unit Level Appropriation Transfers,775B15,Appropriations Transfer Btwn Units One Time,2026-07-01 13:38:24.871
+780010,Intercampus Recharges Expense UCB,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,78000C,Intercampus Recharge Expense C,78000D,Intercampus Recharge Expense D,780010,Intercampus Recharges Expense UCB,2026-07-01 13:38:24.871
+780020,Intercampus Recharges Expense UCSF,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,78000C,Intercampus Recharge Expense C,78000D,Intercampus Recharge Expense D,780020,Intercampus Recharges Expense UCSF,2026-07-01 13:38:24.871
+780115,Interlocation Transfer of Funds Debit UCOP,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,78000C,Intercampus Recharge Expense C,78001D,Interlocation Transfer of Funds Debit D,780115,Interlocation Transfer of Funds Debit UCOP,2026-07-01 13:38:24.871
+780120,Intercampus Recharges Expense ANR,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77000B,Recharge Expense,78000C,Intercampus Recharge Expense C,78000D,Intercampus Recharge Expense D,780120,Intercampus Recharges Expense ANR,2026-07-01 13:38:24.871
+785010,Intercampus Recharges Credit UCB,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78500D,Intercampus Recharges Credit D,785010,Intercampus Recharges Credit UCB,2026-07-01 13:38:24.871
+785020,Intercampus Recharges Credit UCSF,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78500D,Intercampus Recharges Credit D,785020,Intercampus Recharges Credit UCSF,2026-07-01 13:38:24.871
+785040,Intercampus Recharges Credit UCLA,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78500D,Intercampus Recharges Credit D,785040,Intercampus Recharges Credit UCLA,2026-07-01 13:38:24.871
+785045,Interlocation Transfer of Funds Credit UCLA,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78501D,Interlocation Transfer of Funds Credit,785045,Interlocation Transfer of Funds Credit UCLA,2026-07-01 13:38:24.871
+785060,Intercampus Recharges Credit UCSD,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78500D,Intercampus Recharges Credit D,785060,Intercampus Recharges Credit UCSD,2026-07-01 13:38:24.871
+785110,Intercampus Recharges Credit UCOP,5XXXXX,All Expenses,77000A,Recharge Revenues and Expenses,77500B,Recharge Revenue,78500C,Intercampus Recharges Credit C,78500D,Intercampus Recharges Credit D,785110,Intercampus Recharges Credit UCOP,2026-07-01 13:38:24.871
diff --git a/server.core/Data/Seed/ActivityHierarchy.csv b/server.core/Data/Seed/ActivityHierarchy.csv
new file mode 100644
index 0000000..90e0f3c
--- /dev/null
+++ b/server.core/Data/Seed/ActivityHierarchy.csv
@@ -0,0 +1,40 @@
+Code,Description,ParentLevel0Code,ParentLevel0Name,ParentLevel1Code,ParentLevel1Name,ParentLevel2Code,ParentLevel2Name,ParentLevel3Code,ParentLevel3Name,ParentLevel4Code,ParentLevel4Name,ParentLevel5Code,ParentLevel5Name,LoadedAt
+SUMQTR,Summer Quarter,10000A,All Activities,20040B,Academic Terms,SUMQTR,Summer Quarter,SUMQTR,Summer Quarter,SUMQTR,Summer Quarter,SUMQTR,Summer Quarter,2026-07-01 13:38:34.069
+202042,France Berkeley,10000A,All Activities,20201B,Funding Allocations,202042,France Berkeley,202042,France Berkeley,202042,France Berkeley,202042,France Berkeley,2026-07-01 13:38:34.069
+000000,Default Activity Value,10000A,All Activities,00000B,Default Activity,000000,Default Activity Value,000000,Default Activity Value,000000,Default Activity Value,000000,Default Activity Value,2026-07-01 13:38:34.069
+300101,Temp Teaching,10000A,All Activities,30001B,Academic and Instruction,300101,Temp Teaching,300101,Temp Teaching,300101,Temp Teaching,300101,Temp Teaching,2026-07-01 13:38:34.069
+202024,Other Faculty Awards,10000A,All Activities,20201B,Funding Allocations,202024,Other Faculty Awards,202024,Other Faculty Awards,202024,Other Faculty Awards,202024,Other Faculty Awards,2026-07-01 13:38:34.069
+211033,NUT Nutritional Biology GSA,10000A,All Activities,22100B,Graduate Student Associations and Grad Groups,211033,NUT Nutritional Biology GSA,211033,NUT Nutritional Biology GSA,211033,NUT Nutritional Biology GSA,211033,NUT Nutritional Biology GSA,2026-07-01 13:38:34.069
+202011,Faculty Academic Enrichment Prof Develop,10000A,All Activities,20201B,Funding Allocations,202011,Faculty Academic Enrichment Prof Develop,202011,Faculty Academic Enrichment Prof Develop,202011,Faculty Academic Enrichment Prof Develop,202011,Faculty Academic Enrichment Prof Develop,2026-07-01 13:38:34.069
+202019,Academic Senate Grants,10000A,All Activities,20201B,Funding Allocations,202019,Academic Senate Grants,202019,Academic Senate Grants,202019,Academic Senate Grants,202019,Academic Senate Grants,2026-07-01 13:38:34.069
+200516,Engagement and Outreach,10000A,All Activities,20050B,Departmental Operations,200516,Engagement and Outreach,200516,Engagement and Outreach,200516,Engagement and Outreach,200516,Engagement and Outreach,2026-07-01 13:38:34.069
+202055,Academic Federation Grants,10000A,All Activities,20201B,Funding Allocations,202055,Academic Federation Grants,202055,Academic Federation Grants,202055,Academic Federation Grants,202055,Academic Federation Grants,2026-07-01 13:38:34.069
+200638,Teaching Assistants,10000A,All Activities,20050B,Departmental Operations,200638,Teaching Assistants,200638,Teaching Assistants,200638,Teaching Assistants,200638,Teaching Assistants,2026-07-01 13:38:34.069
+202069,Chancellor Fellow Award,10000A,All Activities,20201B,Funding Allocations,202069,Chancellor Fellow Award,202069,Chancellor Fellow Award,202069,Chancellor Fellow Award,202069,Chancellor Fellow Award,2026-07-01 13:38:34.069
+300007,Student Advising,10000A,All Activities,30001B,Academic and Instruction,300007,Student Advising,300007,Student Advising,300007,Student Advising,300007,Student Advising,2026-07-01 13:38:34.069
+202044,Salary Savings,10000A,All Activities,20201B,Funding Allocations,202044,Salary Savings,202044,Salary Savings,202044,Salary Savings,202044,Salary Savings,2026-07-01 13:38:34.069
+202012,Faculty Start Up,10000A,All Activities,20201B,Funding Allocations,202012,Faculty Start Up,202012,Faculty Start Up,202012,Faculty Start Up,202012,Faculty Start Up,2026-07-01 13:38:34.069
+202013,Leadership Support eg Dept Chair,10000A,All Activities,20201B,Funding Allocations,202013,Leadership Support eg Dept Chair,202013,Leadership Support eg Dept Chair,202013,Leadership Support eg Dept Chair,202013,Leadership Support eg Dept Chair,2026-07-01 13:38:34.069
+202023,Seed Grant,10000A,All Activities,20201B,Funding Allocations,202023,Seed Grant,202023,Seed Grant,202023,Seed Grant,202023,Seed Grant,2026-07-01 13:38:34.069
+200101,Resident Graduate Student Recruiting and Outreach,10000A,All Activities,20010B,Recruiting,200101,Resident Graduate Student Recruiting and Outreach,200101,Resident Graduate Student Recruiting and Outreach,200101,Resident Graduate Student Recruiting and Outreach,200101,Resident Graduate Student Recruiting and Outreach,2026-07-01 13:38:34.069
+201537,Centralized Gift Processing,10000A,All Activities,20150B,Development Activities B,201537,Centralized Gift Processing,201537,Centralized Gift Processing,201537,Centralized Gift Processing,201537,Centralized Gift Processing,2026-07-01 13:38:34.069
+990002,UC Path Error,10000A,All Activities,99000B,UC Path Errors,990002,UC Path Error,990002,UC Path Error,990002,UC Path Error,990002,UC Path Error,2026-07-01 13:38:34.069
+990000,UC Path Default,10000A,All Activities,99000B,UC Path Errors,990000,UC Path Default,990000,UC Path Default,990000,UC Path Default,990000,UC Path Default,2026-07-01 13:38:34.069
+202010,Faculty Research,10000A,All Activities,20201B,Funding Allocations,202010,Faculty Research,202010,Faculty Research,202010,Faculty Research,202010,Faculty Research,2026-07-01 13:38:34.069
+202037,Base Funded Staff,10000A,All Activities,20201B,Funding Allocations,202037,Base Funded Staff,202037,Base Funded Staff,202037,Base Funded Staff,202037,Base Funded Staff,2026-07-01 13:38:34.069
+202052,Matching Funds,10000A,All Activities,20201B,Funding Allocations,202052,Matching Funds,202052,Matching Funds,202052,Matching Funds,202052,Matching Funds,2026-07-01 13:38:34.069
+900031,Professional Development,10000A,All Activities,90000B,Agricultural Activities,900031,Professional Development,900031,Professional Development,900031,Professional Development,900031,Professional Development,2026-07-01 13:38:34.069
+202102,Classroom Instruction,10000A,All Activities,20201B,Funding Allocations,202102,Classroom Instruction,202102,Classroom Instruction,202102,Classroom Instruction,202102,Classroom Instruction,2026-07-01 13:38:34.069
+200569,Assessments,10000A,All Activities,20050B,Departmental Operations,200569,Assessments,200569,Assessments,200569,Assessments,200569,Assessments,2026-07-01 13:38:34.069
+202177,NIFA Cost Share,10000A,All Activities,20201B,Funding Allocations,202177,NIFA Cost Share,202177,NIFA Cost Share,202177,NIFA Cost Share,202177,NIFA Cost Share,2026-07-01 13:38:34.069
+202075,Library Open Access,10000A,All Activities,20201B,Funding Allocations,202075,Library Open Access,202075,Library Open Access,202075,Library Open Access,202075,Library Open Access,2026-07-01 13:38:34.069
+202014,Faculty Bridge Funding,10000A,All Activities,20201B,Funding Allocations,202014,Faculty Bridge Funding,202014,Faculty Bridge Funding,202014,Faculty Bridge Funding,202014,Faculty Bridge Funding,2026-07-01 13:38:34.069
+202031,Instructional Equipment,10000A,All Activities,20201B,Funding Allocations,202031,Instructional Equipment,202031,Instructional Equipment,202031,Instructional Equipment,202031,Instructional Equipment,2026-07-01 13:38:34.069
+990001,UC Path Suspense,10000A,All Activities,99000B,UC Path Errors,990001,UC Path Suspense,990001,UC Path Suspense,990001,UC Path Suspense,990001,UC Path Suspense,2026-07-01 13:38:34.069
+100029,Departmental Events,10000A,All Activities,10000B,Campus Events,100029,Departmental Events,100029,Departmental Events,100029,Departmental Events,100029,Departmental Events,2026-07-01 13:38:34.069
+200537,Allocated Benefits,10000A,All Activities,20050B,Departmental Operations,200537,Allocated Benefits,200537,Allocated Benefits,200537,Allocated Benefits,200537,Allocated Benefits,2026-07-01 13:38:34.069
+202015,Students Support Provisions,10000A,All Activities,20201B,Funding Allocations,202015,Students Support Provisions,202015,Students Support Provisions,202015,Students Support Provisions,202015,Students Support Provisions,2026-07-01 13:38:34.069
+202046,Programmatic Initiative,10000A,All Activities,20201B,Funding Allocations,202046,Programmatic Initiative,202046,Programmatic Initiative,202046,Programmatic Initiative,202046,Programmatic Initiative,2026-07-01 13:38:34.069
+202038,Student Awards,10000A,All Activities,20201B,Funding Allocations,202038,Student Awards,202038,Student Awards,202038,Student Awards,202038,Student Awards,2026-07-01 13:38:34.069
+900029,Professional Travel Grants,10000A,All Activities,90000B,Agricultural Activities,900029,Professional Travel Grants,900029,Professional Travel Grants,900029,Professional Travel Grants,900029,Professional Travel Grants,2026-07-01 13:38:34.069
+202082,Faculty Recruitment,10000A,All Activities,20201B,Funding Allocations,202082,Faculty Recruitment,202082,Faculty Recruitment,202082,Faculty Recruitment,202082,Faculty Recruitment,2026-07-01 13:38:34.069
diff --git a/server.core/Data/Seed/DepartmentSource.csv b/server.core/Data/Seed/DepartmentSource.csv
new file mode 100644
index 0000000..79dcf8e
--- /dev/null
+++ b/server.core/Data/Seed/DepartmentSource.csv
@@ -0,0 +1,4 @@
+FinancialDeptDCode,FinancialDeptDName,FinancialDeptECode,FinancialDeptEName,FinancialDeptFCode,FinancialDeptFName,FinancialDeptGCode,FinancialDeptGName,Fund,FundName,Program,ProgramName,Activity,ActivityName,Project,ProjectName,NaturalAccount,NaturalAccountName,PeriodName,FiscalYear,IncomeAmount,ExpenseAmount,LoadedAt
+ACL100D,AAES ACL1 Food Chain Cluster D,ANUT00E,AAES ANUT Nutrition E,ANUT00F,AAES ANUT Nutrition F,ANUT006,ANUT GNUB Nutritional Biology Grad Group,13U02,COFI University Common Funds AES19900 99300,000,Default Program Value,202037,Base Funded Staff,0000000000,Default Project,508301,Vacation and Leave Usage Salaries and Wages,Jul-24,2025,0.00,-737.93,2026-06-24 19:17:34.830
+ACL100D,AAES ACL1 Food Chain Cluster D,ANUT00E,AAES ANUT Nutrition E,ANUT00F,AAES ANUT Nutrition F,ANUT006,ANUT GNUB Nutritional Biology Grad Group,13U02,COFI University Common Funds AES19900 99300,000,Default Program Value,202037,Base Funded Staff,0000000000,Default Project,508300,Vacation Accrued Expenses,Jul-24,2025,0.00,626.27,2026-06-24 19:17:34.830
+ACL100D,AAES ACL1 Food Chain Cluster D,ANUT00E,AAES ANUT Nutrition E,ANUT00F,AAES ANUT Nutrition F,ANUT006,ANUT GNUB Nutritional Biology Grad Group,13U02,COFI University Common Funds AES19900 99300,000,Default Program Value,202037,Base Funded Staff,0000000000,Default Project,505500,UCRP Supplemental Assessment Benefits Expense Interest,Jul-24,2025,0.00,54.67,2026-06-24 19:17:34.830
diff --git a/server.core/Data/Seed/ErnCodes.csv b/server.core/Data/Seed/ErnCodes.csv
new file mode 100644
index 0000000..8ba0505
--- /dev/null
+++ b/server.core/Data/Seed/ErnCodes.csv
@@ -0,0 +1,251 @@
+DOS_Code,Description,IncludeInAD419FTE,IsNewInUCP
+8EF,Retro-EPSL-8EF,true,1
+901,Retro Regular Pay-Non Base,false,1
+92L,Retro Holiday-Premium Hourly,false,
+944,Retro Stipend-Admin-Academic,false,1
+94H,NRA Retro Regular Pay,false,1
+962,Retro Fellowship Qual Non-Tax,false,1
+968,Retro Neg Sal Plan-Additional,false,
+980,Retro Supplement-Post Doc,false,
+98J,NRA Retro Catastrophic Lv Sal,true,
+98N,NRA Retro Vacation Leave-SAL,true,
+99T,Terminal Vacation-Pay NRA19Ret,false,
+9A1,Retro Administrative Leave Hrl,false,1
+9AD,Retro Administrative Leave Sal,false,
+9AF,Retro Addl Comp-FY Research,false,1
+9AS,Retro Addl Comp-Summer-No 403,false,
+9CC,Retro Addl Comp-Research,false,1
+9CO,Retro - Comp Time Used Hrly,true,1
+9ET,Terminal Vacation-Pay NRA18Ret,false,
+9HL,Retro Holiday-Regular Hourly,false,1
+9HO,Retro Holiday-Regular Salaried,false,1
+9HS,Retro HSCP-Negotiated (Y),false,
+9J8,NRA Retro Sick Leave Salaried,true,
+9JN,Retro Jury Duty-Hourly,true,
+9JR,Retro Jury Duty Salaried,true,1
+9NC,Retro Negotiated Sal Plan-Reg All,false,
+9NE,Retro-EPSL-9NE,true,
+9OD,Retro Overtime Pay-Double,false,
+9OP,Retro - Overtime Pay Premium,false,1
+9OS,Retro - Overtime Pay Straight,false,1
+9PB,Retro-PFCB,false,
+9PL,Retro-EPSL-9PL,true,
+9PO,Retro Paid Time-Off Hourly,false,
+9PP,Retro - Premium Pay at Prem,false,
+9PS,Retro -Premium Pay at Straight,false,
+9R8,NRA Retro Regular Pay,false,1
+9R9,NRA Retro - Regular Pay,false,1
+9RC,RETRO Paid LV Salar,false,1
+9RG,Retro - Regular Pay,false,1
+9RT,Retro Paid Time-Off Salaried,true,
+9RV,RETRO Paid Lve-Hrly,false,1
+9SA,Retro Stipend-Admin-Staff,false,1
+9SF,Retro-EPSL-9SF,true,
+9SK,Retro Sick Leave Used Sal,true,1
+9SL,Retro - Sick Leave-Used Hrly,true,1
+9SO,Retro HSCP Off-Scale Earnings,false,
+9SP,Retro HSCP-Diff (X Prime),false,
+9SR,Retro HSCP-Regular (X),false,
+9TM,Terminal Vacation-Pay Retro,false,
+9VC,Retro-Vacation Leave-Used Sal,false,1
+9VL,Retro Catastrophic Leave Taken,true,
+9VN,Retro-Vacation Leave-Used Hrly,false,1
+9X0,NRA Retro HOL-Regular Hourly,false,
+9X1,NRA Retro HOL-Regular Hourly,false,
+A1N,Administrative Leave - Hourly,true,1
+A8R,NRA Additional Comp-Research,false,
+A9A,NRA Additional Comp-SumNo DCP,false,1
+A9N,NRA Additional CompGenNo RTMT,false,
+AAP,Academic Award Program,false,
+ACA,Additional Comp-Admin,false,1
+ACR,Additional Comp-Research,true,0
+ACS,Additional Comp-Summer-DCP,false,1
+ADC,Additional Comp-General,false,1
+ADM,Administrative Leave -Salaried,true,1
+AFR,Additional Comp-FY Research,true,0
+ASN,Additional Comp-Summer-No DCP,false,1
+AWD,Incentive Award,false,0
+B2B,Benefits Billing,false,1
+C08,NRA Comp-Time-Used Hrly,true,
+CCA,Child Care Assistance,false,1
+CON,Compensatory Time-Used Hrly,true,1
+CRT,Certification Pay,false,1
+CTO,Compensatory Time-Used Sal,true,1
+CTP,Compensatory Time-Paid,false,1
+CTT,Compensatory Time-Term,false,1
+DLP,Discretionary Lump Sum,false,1
+DM8,NRA Damage Payment,false,
+DM9,NRA Damage Payment,false,1
+DMG,Damage Payment,false,0
+E7F,Emergency Paid Sick Leave - Fa,true,1
+E8F,Emergency Paid Sick Leave - Fa,true,1
+E8P,Emergency Paid Sick Leave - EE,true,1
+E9F,Emergency Paid Sick Leave - Fa,true,1
+E9P,Emergency Paid Sick Leave - EE,true,1
+EDI,Education-On Site,false,
+EFL,Expanded Family Medical Leave,true,1
+EFN,Emergency Paid Sick Leave - Fa,true,1
+EMN,Expanded Family Medical Leave,true,1
+EN8,Emergency Paid Sick Leave - EE,true,
+EN9,Emergency Paid Sick Leave - EE,true,1
+EPS,Emergency Paid Sick Leave - EE,true,1
+ES8,NRA Evening Shift Differential,false,
+ESD,Evening Shift Differential,false,1
+ESF,Emergency Paid Sick Leave - Fa,true,1
+ESL,Extended Sick Leave,true,1
+ESN,Emergency Paid Sick Leave - EE,true,1
+ETW,Employee Taxable Wages,false,1
+FEL,Fellowship Qual NRA Taxable,false,1
+FEN,Fellowship Qual Non-Tax,false,1
+FRA,Faculty Recruitment Allowance,false,1
+H7L,NRA HOL-Regular Hourly,true,
+H8H,NRA Holiday-Regular Salaried,true,1
+H8I,NRA Holiday-Premium,false,
+H8L,NRA HOL-Regular Hourly,true,1
+H9H,NRA Holiday-Regular Salaried,true,1
+H9I,NRA Holiday-Premium,false,1
+H9L,NRA HOL-Regular Hourly,true,1
+HCP,Holiday-Comp-Time-Paid,false,
+HLN,Holiday-Regular Hourly,true,1
+HLP,Holiday-Premium,false,1
+HLS,Holiday-Straight,false,
+HN9,NRA Honorarium,false,
+HOL,Holiday-Regular Salaried,true,0
+HON,Honorarium,false,1
+HOS,HSCP Off-Scale X Earnings,true,1
+HPD,Holiday-Regular Salaried,true,
+HPH,Holiday-Regular Hourly,true,
+HSG,Housing Allowance,false,
+HSN,HSCP-Negotiated (Y),true,1
+HSP,HSCP-Differential (X Prime),false,1
+HSR,HSCP-Regular (X),true,1
+HSZ,HLTH Sciences-Z (General),false,
+HUN,Holiday-Comp-Time-Used Hrly,true,1
+IHT,Regular-Training Salaried,false,
+ITN,Regular-Training Hourly,false,
+ITP,Involuntary Term-Pay In Lieu,false,
+ITR,Involuntary Term-Regular,false,1
+ITS,Involuntary Term-Severance,false,1
+JRN,Jury Duty-Regular Hourly,true,1
+JUR,Jury Duty - Salaried,true,1
+L7P,NRA Leave-No Pay Salaried,false,
+L8F,NRA Leave-No Pay-FMLA Salaried,true,
+L8P,NRA Leave-No Pay Salaried,false,
+L9P,NRA Leave-No Pay Salaried,false,
+LEC,Leave - Emergency Closure,true,1
+LNF,Leave-No Pay-FMLA Salaried,true,1
+LNP,Leave-No Pay Salaried,true,1
+LS8,NRA Lump Sum-No RET,false,
+LS9,NRA Lump Sum-No RET,false,
+LSN,Lump Sum-No Retirement,false,0
+LSP,Lump Sum-Retirement,false,1
+MIL,Military Pay-Salaried,true,1
+MLN,Military Pay-Hourly,true,1
+NCB,Negotiated Sal Plan-Regular,false,
+NFT,Non-Taxable Fringe Benefit,false,1
+NNC,Negotiated Sal Plan-Additional,false,0
+NSD,Night Shift Differential *,false,1
+NTF,Non-Taxable Fringe Benefit,false,1
+O9D,NRA Overtime Pay-Double,false,
+ODH,Overtime Pay-Double,false,0
+OML,Overmax Lump,false,
+OS8,NRA Overtime Pay-Straight,false,
+OS9,NRA Overtime Pay-Straight,false,1
+OT8,NRA Overtime Pay-Premium,false,
+OT9,NRA Overtime Pay-Premium,false,1
+OTP,Overtime Pay-Premium,false,0
+OTS,Overtime Pay-Straight,false,0
+P18,Postdoc Child careNRA18,false,
+P8T,NRA Paid Time-Off Salaried,true,1
+P9D,NRA Award-Professional Develop,false,
+P9T,NRA Paid Time-Off Salaried,true,
+PB8,NRA-Paid Family Care & Bonding Hourly,true,
+PCA,Police ADV Certif. Pay Hourly,false,1
+PCC,Postdoc Child Care,false,1
+PCO,Police ADV Cert Pay Hourly OT,false,1
+PDA,Award-Professional Development,false,
+PFL,Paid Family Care & Bonding,true,
+PFN,Paid Family Care & Bonding,true,
+PON,Paid Time-Off Hourly,true,1
+PP8,NRA Premium Pay at Premium,false,
+PPP,Premium Pay at Premium,false,1
+PPS,Premium Pay at Straight,false,1
+PRQ,Perq-House-Meal-Deduction,false,1
+PS8,NRA Premium Pay at Straight,false,
+PS9,NRA Premium Pay at Straight,false,
+PTO,Paid Time-Off Salaried,true,0
+PTP,Paid Time Off Payout,false,
+R89,RETRO PAID LV Salar,false,
+RC7,NRA Paid Leave-Salaried,true,1
+RC8,NRA Paid Leave-Salaried,true,1
+RC9,NRA Paid Leave-Salaried,true,1
+REG,Regular Pay,true,0
+RF7,NRA Regular-Flat Amount,false,
+RF9,NRA Regular-Flat Amount,false,1
+RFA,Regular-Flat Amount,false,1
+RG7,NRA Regular Pay,true,1
+RG8,NRA Regular Pay,true,1
+RG9,NRA Regular Pay,true,1
+RGC,REG Paid Leave-Salaried,true,1
+RGN,Regular Pay-Non Base,false,1
+RIP,Relocation Payment,false,1
+RN8,NRA Regular Pay-Non Base,false,
+RN9,NRA Regular Pay-Non Base,false,
+RPC,Reg. Paid Continuation -Salary,true,
+RPN,Reg. Paid Continuation -Hourly,true,1
+RPY,Retention Pay,false,
+RSF,By Agreement - Self Supporting Program,false,
+RV8,REG Paid Leave-Hourly,true,
+RV9,REG Paid Leave-Hourly,true,1
+RVN,REG Paid Leave-Hourly,true,1
+S1L,Sick Leave-Paid Hourly,true,1
+S7N,NRA Sick Leave-Paid Hourly,true,1
+S7P,NRA Staff Physician PayReg,true,1
+S8N,NRA Sick Leave-Paid Hourly,true,1
+S8P,NRA Staff Physician PayReg,true,1
+S9N,NRA Sick Leave-Paid Hourly,true,1
+S9P,NRA Staff Physician PayReg,true,1
+SAS,Stipend-Admin-Staff,false,0
+SKA,PD Sck Leave Accrual,true,1
+SKL,Sick Leave Paid - Salaried,true,0
+SKP,Post Doc Sick Leave,true,1
+SL7,NRA Sick Leave Used-Salaried,true,1
+SL8,NRA Sick Leave Used-Salaried,true,1
+SL9,NRA Sick Leave Paid - Salaried,true,1
+SLA,Sick Leave-Accrued,true,0
+SPD,Stipend-Post Doc,false,1
+SPN,Staff Physician Pay-Non Base,false,1
+SPR,Staff Physician Pay-Regular,true,1
+STP,Stipend-Admin-Academic,false,1
+T20,Time On Call - 20% Hrly Rate,false,1
+TOC,Time On Call,false,0
+TR8,NRA Terminal Vacation-Pay,false,
+TR9,NRA Terminal Vacation-Pay,false,
+TRM,Terminal Vacation-Pay,false,0
+TT9,NRA Compensatory Time-Term,false,
+UNX,University Extensions,false,1
+V7N,NRA Vacation Leave-Used Hourly,true,1
+V8N,NRA Vacation Leave-Used Hourly,true,1
+V9N,NRA Vacation Leave-Used Hourly,true,1
+VAC,Vacation Leave-Used Salaried,true,0
+VC7,NRA Vacatn Leave-Paid Salaried,true,1
+VC8,NRA Vacatn Leave-Paid Salaried,true,1
+VC9,NRA Vacation Leave-Used Salard,true,1
+VCN,Vacation Leave-Used Hourly,true,1
+VL7,NRA Catastrophic Leave Sal,true,1
+VL8,NRA Catastrophic Leave Sal,true,1
+VL9,NRA Catastrophic Leave Taken,true,1
+VLA,Vacation Leave-Accrued,true,0
+VLC,Catastrophic Leave Taken Sal,true,1
+VLN,Catastrophic Leave Taken Hrly,true,1
+VN7,NRA Catastrophic LV Taken Hrly,true,1
+VN8,NRA Catastrophic LV Taken Hrly,true,1
+VN9,NRA Catastrophic LV Taken Hrly,true,1
+VPO,Vacation Payout,false,
+VTS,Voluntary Term-Severance,false,1
+WCP,Workers Comp-Regular,true,1
+WCR,Workers Comp-Reduction,false,0
+WDD,Weekend Day Shift Differential,false,1
+XCI,Clinical Incentive Plan,false,
+XSL,Star Award Local,false,0
+XXX,Not Applicable (N/A),false,1
diff --git a/server.core/Data/Seed/FundHierarchy.csv b/server.core/Data/Seed/FundHierarchy.csv
new file mode 100644
index 0000000..82ee969
--- /dev/null
+++ b/server.core/Data/Seed/FundHierarchy.csv
@@ -0,0 +1,111 @@
+Code,Description,ParentLevel0Code,ParentLevel0Name,ParentLevel1Code,ParentLevel1Name,ParentLevel2Code,ParentLevel2Name,ParentLevel3Code,ParentLevel3Name,ParentLevel4Code,ParentLevel4Name,ParentLevel5Code,ParentLevel5Name,LoadedAt
+71549,Dietetic Food Nutrition Steinberg 49029,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71549,Dietetic Food Nutrition Steinberg 49029,71549,Dietetic Food Nutrition Steinberg 49029,2026-07-01 13:38:29.229
+P0307,Mars Inc Endowed Chair 122332 42291,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0307,Mars Inc Endowed Chair 122332 42291,P0307,Mars Inc Endowed Chair 122332 42291,2026-07-01 13:38:29.229
+71836,Nutrition Research Steinberg 49317,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71836,Nutrition Research Steinberg 49317,71836,Nutrition Research Steinberg 49317,2026-07-01 13:38:29.229
+71864,Nutri Research Keen 49345,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71864,Nutri Research Keen 49345,71864,Nutri Research Keen 49345,2026-07-01 13:38:29.229
+37967,Nutrition Schneeman Res Endowed Fd,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,37967,Nutrition Schneeman Res Endowed Fd,37967,Nutrition Schneeman Res Endowed Fd,2026-07-01 13:38:29.229
+14806,Grad Stud Supplemental Application Fee 20201,1000A,Unrestricted A,1500B,Designated,1480C,Other Student Fees C,1480D,Other Student Fees D,14806,Grad Stud Supplemental Application Fee 20201,14806,Grad Stud Supplemental Application Fee 20201,2026-07-01 13:38:29.229
+P0949,Lingle Family Scholarship 124319 50072,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0949,Lingle Family Scholarship 124319 50072,P0949,Lingle Family Scholarship 124319 50072,2026-07-01 13:38:29.229
+36996,Kristen Maxwell Memorial Award,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36996,Kristen Maxwell Memorial Award,36996,Kristen Maxwell Memorial Award,2026-07-01 13:38:29.229
+63015,KFS Conversion Gift Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,63015,KFS Conversion Gift Fund,63015,KFS Conversion Gift Fund,2026-07-01 13:38:29.229
+66942,Nutr Ctr For Nutrition In Schools 43893,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,66942,Nutr Ctr For Nutrition In Schools 43893,66942,Nutr Ctr For Nutrition In Schools 43893,2026-07-01 13:38:29.229
+76780,SPLASH Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76780,SPLASH Fund,76780,SPLASH Fund,2026-07-01 13:38:29.229
+P0102,Campbell Soup Nutrition Fellowship 111873 40209,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0102,Campbell Soup Nutrition Fellowship 111873 40209,P0102,Campbell Soup Nutrition Fellowship 111873 40209,2026-07-01 13:38:29.229
+76959,Clinical Lactation Program Support Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76959,Clinical Lactation Program Support Fund,76959,Clinical Lactation Program Support Fund,2026-07-01 13:38:29.229
+16303,Employee Support Program Reserve 75041,1000A,Unrestricted A,1500B,Designated,1600C,Campus Designated Reserves C,1620D,Campus Reserves,16303,Employee Support Program Reserve 75041,16303,Employee Support Program Reserve 75041,2026-07-01 13:38:29.229
+P0806,Corinne L Rustici Endowed in Applied 123720 49277,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0806,Corinne L Rustici Endowed in Applied 123720 49277,P0806,Corinne L Rustici Endowed in Applied 123720 49277,2026-07-01 13:38:29.229
+37988,Schwall Floyd and Mary Fellowship,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,37988,Schwall Floyd and Mary Fellowship,37988,Schwall Floyd and Mary Fellowship,2026-07-01 13:38:29.229
+37353,Nutrition Dept Research End,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,37353,Nutrition Dept Research End,37353,Nutrition Dept Research End,2026-07-01 13:38:29.229
+18072,California Cancer Research,2000A,Restricted Expendable,1800B,Special State Appropriations B,1800C,Special State Appropriations C,1800D,Special State Appropriations D,18072,California Cancer Research,18072,California Cancer Research,2026-07-01 13:38:29.229
+74223,Nutrition Research Fund 55981,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,74223,Nutrition Research Fund 55981,74223,Nutrition Research Fund 55981,2026-07-01 13:38:29.229
+20403,State and Local Grant Flowthrough,2000A,Restricted Expendable,2000B,ContractsandGrants,2040C,State and Local Contracts and Grants C,2040D,State and Local Contracts and Grants D,20403,State and Local Grant Flowthrough,20403,State and Local Grant Flowthrough,2026-07-01 13:38:29.229
+71912,Diabetic Research In Rats 49392,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71912,Diabetic Research In Rats 49392,71912,Diabetic Research In Rats 49392,2026-07-01 13:38:29.229
+38434,Nutrition Taubeneck Angel2,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,38434,Nutrition Taubeneck Angel2,38434,Nutrition Taubeneck Angel2,2026-07-01 13:38:29.229
+70677,Animal Science Nutrition Rsearch Ji 48155,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,70677,Animal Science Nutrition Rsearch Ji 48155,70677,Animal Science Nutrition Rsearch Ji 48155,2026-07-01 13:38:29.229
+20000,Federal Grants,2000A,Restricted Expendable,2000B,ContractsandGrants,2000C,Federal ContractsandGrants C,2000D,Federal ContractsandGrants D,20000,Federal Grants,20000,Federal Grants,2026-07-01 13:38:29.229
+E0083,PRN UCD Health Eye Center Education Fund 322947 44626,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,E000D,Restricted Expendable Gifts Foundation,E0083,PRN UCD Health Eye Center Education Fund 322947 44626,E0083,PRN UCD Health Eye Center Education Fund 322947 44626,2026-07-01 13:38:29.229
+P0644,Dr William Weir Summer Research Award 123129 49926,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0644,Dr William Weir Summer Research Award 123129 49926,P0644,Dr William Weir Summer Research Award 123129 49926,2026-07-01 13:38:29.229
+20701,Private Contract Flowthrough,2000A,Restricted Expendable,2000B,ContractsandGrants,2070C,Private ContactsandGrants C,2070D,Private ContactsandGrants D,20701,Private Contract Flowthrough,20701,Private Contract Flowthrough,2026-07-01 13:38:29.229
+12100,Sales and Services Rate Based,1000A,Unrestricted A,1000B,Unrestricted B,1200C,SalesandServices Other,1210D,SalesandServices D,12100,Sales and Services Rate Based,12100,Sales and Services Rate Based,2026-07-01 13:38:29.229
+71880,Nutri Res and Edu Havel 49361,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71880,Nutri Res and Edu Havel 49361,71880,Nutri Res and Edu Havel 49361,2026-07-01 13:38:29.229
+P1069,Gall Faculty Research Endowment 124758 56594,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P1069,Gall Faculty Research Endowment 124758 56594,P1069,Gall Faculty Research Endowment 124758 56594,2026-07-01 13:38:29.229
+16200,Sales and Service Reserves,1000A,Unrestricted A,1500B,Designated,1600C,Campus Designated Reserves C,1620D,Campus Reserves,16200,Sales and Service Reserves,16200,Sales and Service Reserves,2026-07-01 13:38:29.229
+13366,Alfred and Alice Lachmann IntL Nutrition,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,13366,Alfred and Alice Lachmann IntL Nutrition,13366,Alfred and Alice Lachmann IntL Nutrition,2026-07-01 13:38:29.229
+13U34,UC Path Default 67721,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U2D,Unrestricted General,13U34,UC Path Default 67721,13U34,UC Path Default 67721,2026-07-01 13:38:29.229
+P1065,Rucker Family Fellowship 124728 45978,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P1065,Rucker Family Fellowship 124728 45978,P1065,Rucker Family Fellowship 124728 45978,2026-07-01 13:38:29.229
+74842,Nutrition Research and Education Fund 56595,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,74842,Nutrition Research and Education Fund 56595,74842,Nutrition Research and Education Fund 56595,2026-07-01 13:38:29.229
+P0103,H J Heinz Endowed Student Fund 111875 40266,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0103,H J Heinz Endowed Student Fund 111875 40266,P0103,H J Heinz Endowed Student Fund 111875 40266,2026-07-01 13:38:29.229
+14400,Course Materials and Services Fees,1000A,Unrestricted A,1500B,Designated,1440C,Course Materials and Services Fees,1440D,Course Materials and Services Fees D,14400,Course Materials and Services Fees,14400,Course Materials and Services Fees,2026-07-01 13:38:29.229
+36327,Schp Com Jastro Mem Endow,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36327,Schp Com Jastro Mem Endow,36327,Schp Com Jastro Mem Endow,2026-07-01 13:38:29.229
+P0305,Barbara J Van Zandt End 122330 42290,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0305,Barbara J Van Zandt End 122330 42290,P0305,Barbara J Van Zandt End 122330 42290,2026-07-01 13:38:29.229
+64074,An Sci Org Res Berger 41054,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,64074,An Sci Org Res Berger 41054,64074,An Sci Org Res Berger 41054,2026-07-01 13:38:29.229
+06272,Nutrition Kosuna Endowment 06272,2000A,Restricted Expendable,2200B,Restricted Gifts B,8000C,Restricted Funds Functioning as Endowment UCOP and Foundations only,8000D,Restricted FFE Principal Regents ,06272,Nutrition Kosuna Endowment 06272,06272,Nutrition Kosuna Endowment 06272,2026-07-01 13:38:29.229
+68818,Nutrition Research Oteiza 45771,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,68818,Nutrition Research Oteiza 45771,68818,Nutrition Research Oteiza 45771,2026-07-01 13:38:29.229
+76907,Zinc Nutritional Research,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76907,Zinc Nutritional Research,76907,Zinc Nutritional Research,2026-07-01 13:38:29.229
+P1878,Carl Keen Graduate Student Award in Nutrition 125354 ,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P1878,Carl Keen Graduate Student Award in Nutrition 125354 ,P1878,Carl Keen Graduate Student Award in Nutrition 125354 ,2026-07-01 13:38:29.229
+19921,Online Education,2000A,Restricted Expendable,1999B,Specific State Appropriations B,1999C,Specific State Appropriations C,1999D,Specific State Appropriations D,19921,Online Education,19921,Online Education,2026-07-01 13:38:29.229
+20402,State and Local Contract Flowthrough,2000A,Restricted Expendable,2000B,ContractsandGrants,2040C,State and Local Contracts and Grants C,2040D,State and Local Contracts and Grants D,20402,State and Local Contract Flowthrough,20402,State and Local Contract Flowthrough,2026-07-01 13:38:29.229
+20702,Private Grant Flowthrough,2000A,Restricted Expendable,2000B,ContractsandGrants,2070C,Private ContactsandGrants C,2070D,Private ContactsandGrants D,20702,Private Grant Flowthrough,20702,Private Grant Flowthrough,2026-07-01 13:38:29.229
+13659,Carol Green Sundance Ranch Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,13659,Carol Green Sundance Ranch Fund,13659,Carol Green Sundance Ranch Fund,2026-07-01 13:38:29.229
+12101,Sales and Services NonRate Activities,1000A,Unrestricted A,1000B,Unrestricted B,1200C,SalesandServices Other,1210D,SalesandServices D,12101,Sales and Services NonRate Activities,12101,Sales and Services NonRate Activities,2026-07-01 13:38:29.229
+16551,Pmcf UCD Mohini Jain Pres Chair Match,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,16551,Pmcf UCD Mohini Jain Pres Chair Match,16551,Pmcf UCD Mohini Jain Pres Chair Match,2026-07-01 13:38:29.229
+20700,Private Contracts and Grants,2000A,Restricted Expendable,2000B,ContractsandGrants,2070C,Private ContactsandGrants C,2070D,Private ContactsandGrants D,20700,Private Contracts and Grants,20700,Private Contracts and Grants,2026-07-01 13:38:29.229
+13U58,UCD Education and Research 69640,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U2D,Unrestricted General,13U58,UCD Education and Research 69640,13U58,UCD Education and Research 69640,2026-07-01 13:38:29.229
+37707,Florence S Ragle Res Fd,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,37707,Florence S Ragle Res Fd,37707,Florence S Ragle Res Fd,2026-07-01 13:38:29.229
+67672,Human Health Fund 44626,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,67672,Human Health Fund 44626,67672,Human Health Fund 44626,2026-07-01 13:38:29.229
+63482,Nutri Research Nat Food Extrct Hackma 40480,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,63482,Nutri Research Nat Food Extrct Hackma 40480,63482,Nutri Research Nat Food Extrct Hackma 40480,2026-07-01 13:38:29.229
+76555,Protein Metabolism and Health Research Fund 50596,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76555,Protein Metabolism and Health Research Fund 50596,76555,Protein Metabolism and Health Research Fund 50596,2026-07-01 13:38:29.229
+19986,Student Mental Health Programs 2 3,2000A,Restricted Expendable,1999B,Specific State Appropriations B,1999C,Specific State Appropriations C,1999D,Specific State Appropriations D,19986,Student Mental Health Programs 2 3,19986,Student Mental Health Programs 2 3,2026-07-01 13:38:29.229
+75754,Clinical Breastfeeding Research Fund 48007,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,75754,Clinical Breastfeeding Research Fund 48007,75754,Clinical Breastfeeding Research Fund 48007,2026-07-01 13:38:29.229
+13U00,COFI University Common Funds 99100,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U0D,Common Operating Fund Initiative COFI,13U00,COFI University Common Funds 99100,13U00,COFI University Common Funds 99100,2026-07-01 13:38:29.229
+76309,Milk Bioactive Research Fund 50431,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76309,Milk Bioactive Research Fund 50431,76309,Milk Bioactive Research Fund 50431,2026-07-01 13:38:29.229
+76881,Myrosinase Research Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76881,Myrosinase Research Fund,76881,Myrosinase Research Fund,2026-07-01 13:38:29.229
+71359,Resistant Starch Wheat Research Program 48838,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71359,Resistant Starch Wheat Research Program 48838,71359,Resistant Starch Wheat Research Program 48838,2026-07-01 13:38:29.229
+E1032,PRN Resnick Agricultural Innovation Research 325187 47976,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,E000D,Restricted Expendable Gifts Foundation,E1032,PRN Resnick Agricultural Innovation Research 325187 47976,E1032,PRN Resnick Agricultural Innovation Research 325187 47976,2026-07-01 13:38:29.229
+P0805,Corinne L Rustici Endowed in Human Nut 123719 49276,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0805,Corinne L Rustici Endowed in Human Nut 123719 49276,P0805,Corinne L Rustici Endowed in Human Nut 123719 49276,2026-07-01 13:38:29.229
+71435,Human Nutrition Fund 48915,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71435,Human Nutrition Fund 48915,71435,Human Nutrition Fund 48915,2026-07-01 13:38:29.229
+76862,Rosy Red Valencia Research Study,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76862,Rosy Red Valencia Research Study,76862,Rosy Red Valencia Research Study,2026-07-01 13:38:29.229
+P1318,Mohini Jain Presidential Chair 150304 49294,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P1318,Mohini Jain Presidential Chair 150304 49294,P1318,Mohini Jain Presidential Chair 150304 49294,2026-07-01 13:38:29.229
+65727,Human Lactation Center 42678,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,65727,Human Lactation Center 42678,65727,Human Lactation Center 42678,2026-07-01 13:38:29.229
+E0124,PRN Nutrition General Support 323086 43274,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,E000D,Restricted Expendable Gifts Foundation,E0124,PRN Nutrition General Support 323086 43274,E0124,PRN Nutrition General Support 323086 43274,2026-07-01 13:38:29.229
+20003,Federal Contracts,2000A,Restricted Expendable,2000B,ContractsandGrants,2000C,Federal ContractsandGrants C,2000D,Federal ContractsandGrants D,20003,Federal Contracts,20003,Federal Contracts,2026-07-01 13:38:29.229
+75579,Nuts and Berries Conference Support Fund 46312,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,75579,Nuts and Berries Conference Support Fund 46312,75579,Nuts and Berries Conference Support Fund 46312,2026-07-01 13:38:29.229
+37495,Nutrition Prophet Zeman Endowment,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,37495,Nutrition Prophet Zeman Endowment,37495,Nutrition Prophet Zeman Endowment,2026-07-01 13:38:29.229
+20400,State Contracts and Grants,2000A,Restricted Expendable,2000B,ContractsandGrants,2040C,State and Local Contracts and Grants C,2040D,State and Local Contracts and Grants D,20400,State Contracts and Grants,20400,State Contracts and Grants,2026-07-01 13:38:29.229
+71847,Chrysolide Sciences Nutritional Biology 49328,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71847,Chrysolide Sciences Nutritional Biology 49328,71847,Chrysolide Sciences Nutritional Biology 49328,2026-07-01 13:38:29.229
+71442,Dr Cesar Fraga Research 48922,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,71442,Dr Cesar Fraga Research 48922,71442,Dr Cesar Fraga Research 48922,2026-07-01 13:38:29.229
+39423,Presidents Chair Endowment Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,39423,Presidents Chair Endowment Fund,39423,Presidents Chair Endowment Fund,2026-07-01 13:38:29.229
+36236,Lucille S Hurley Endowment,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36236,Lucille S Hurley Endowment,36236,Lucille S Hurley Endowment,2026-07-01 13:38:29.229
+15001,Med Comp Plan Current Yr Inc 60103,1000A,Unrestricted A,1500B,Designated,1500C,Med Comp Plan C,1500D,Clinical Operations,15001,Med Comp Plan Current Yr Inc 60103,15001,Med Comp Plan Current Yr Inc 60103,2026-07-01 13:38:29.229
+38436,Nutrition Taubeneck Angel1,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,38436,Nutrition Taubeneck Angel1,38436,Nutrition Taubeneck Angel1,2026-07-01 13:38:29.229
+13U02,COFI University Common Funds AES19900 99300,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U0D,Common Operating Fund Initiative COFI,13U02,COFI University Common Funds AES19900 99300,13U02,COFI University Common Funds AES19900 99300,2026-07-01 13:38:29.229
+76756,IMGC Milk Science Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76756,IMGC Milk Science Fund,76756,IMGC Milk Science Fund,2026-07-01 13:38:29.229
+99U99,Error Clearing Fund,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U1D,Unrestricted Control Other,99U99,Error Clearing Fund,99U99,Error Clearing Fund,2026-07-01 13:38:29.229
+E1618,PRN Litov Research and Mentoring Excellence Sppt Fund325875,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,E000D,Restricted Expendable Gifts Foundation,E1618,PRN Litov Research and Mentoring Excellence Sppt Fund325875,E1618,PRN Litov Research and Mentoring Excellence Sppt Fund325875,2026-07-01 13:38:29.229
+09499,University Fund,1000A,Unrestricted A,1000B,Unrestricted B,1020C,Unrestricted Funds Functioning as Endowment for UCOPandFoundationC,1024D,Unrestricted FFE Regents Principal,09499,University Fund,09499,University Fund,2026-07-01 13:38:29.229
+13U10,Unrestricted Campus Control Fund 00200,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U1D,Unrestricted Control Other,13U10,Unrestricted Campus Control Fund 00200,13U10,Unrestricted Campus Control Fund 00200,2026-07-01 13:38:29.229
+13U20,Unrestricted Campus Funds,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U2D,Unrestricted General,13U20,Unrestricted Campus Funds,13U20,Unrestricted Campus Funds,2026-07-01 13:38:29.229
+12120,Program Income Rates,1000A,Unrestricted A,1000B,Unrestricted B,1200C,SalesandServices Other,1211D,Program Income,12120,Program Income Rates,12120,Program Income Rates,2026-07-01 13:38:29.229
+13U30,Departmental STIP,1000A,Unrestricted A,1000B,Unrestricted B,1300C,Other Unrestricted C,13U2D,Unrestricted General,13U30,Departmental STIP,13U30,Departmental STIP,2026-07-01 13:38:29.229
+19924,Student Academic Preparation Programs,2000A,Restricted Expendable,1999B,Specific State Appropriations B,1999C,Specific State Appropriations C,1999D,Specific State Appropriations D,19924,Student Academic Preparation Programs,19924,Student Academic Preparation Programs,2026-07-01 13:38:29.229
+38926,Vitamin Class Action Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,38926,Vitamin Class Action Fund,38926,Vitamin Class Action Fund,2026-07-01 13:38:29.229
+66319,Nutr General Department Support 43274,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,66319,Nutr General Department Support 43274,66319,Nutr General Department Support 43274,2026-07-01 13:38:29.229
+70501,Plant Foods and Vascular Health Rsch 47882,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,70501,Plant Foods and Vascular Health Rsch 47882,70501,Plant Foods and Vascular Health Rsch 47882,2026-07-01 13:38:29.229
+38437,Nutrition Taubeneck Angel3,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,38437,Nutrition Taubeneck Angel3,38437,Nutrition Taubeneck Angel3,2026-07-01 13:38:29.229
+36474,John E Kinsella Endowed Chair,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36474,John E Kinsella Endowed Chair,36474,John E Kinsella Endowed Chair,2026-07-01 13:38:29.229
+76726,Big Bang Theory TBBT Graduate Fellowship Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76726,Big Bang Theory TBBT Graduate Fellowship Fund,76726,Big Bang Theory TBBT Graduate Fellowship Fund,2026-07-01 13:38:29.229
+34900,Nutr Carolyn K Clifford Memorial End,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,34900,Nutr Carolyn K Clifford Memorial End,34900,Nutr Carolyn K Clifford Memorial End,2026-07-01 13:38:29.229
+66320,Nutr Student Support 43275,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,66320,Nutr Student Support 43275,66320,Nutr Student Support 43275,2026-07-01 13:38:29.229
+34542,Cliff and Jane Carpenter Mem Flshp,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,34542,Cliff and Jane Carpenter Mem Flshp,34542,Cliff and Jane Carpenter Mem Flshp,2026-07-01 13:38:29.229
+36272,Nutrition Kosuna Endowment,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36272,Nutrition Kosuna Endowment,36272,Nutrition Kosuna Endowment,2026-07-01 13:38:29.229
+P0807,C Rustici Endowmt Nutrition and Dietetic 123721 49278,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0807,C Rustici Endowmt Nutrition and Dietetic 123721 49278,P0807,C Rustici Endowmt Nutrition and Dietetic 123721 49278,2026-07-01 13:38:29.229
+36057,Nutr Fredric Hill Endowment,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,36057,Nutr Fredric Hill Endowment,36057,Nutr Fredric Hill Endowment,2026-07-01 13:38:29.229
+14305,SSDP Maternal and Child Nutrition 20246,1000A,Unrestricted A,1500B,Designated,1430C,Self Supporting Degree Program Fee C,1430D,Self Supporting Degree Program Fee D,14305,SSDP Maternal and Child Nutrition 20246,14305,SSDP Maternal and Child Nutrition 20246,2026-07-01 13:38:29.229
+P0760,Award for Innovation Creative Vision 123539 55065,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,P000D,Restricted Gift Funds UC Davis Foundation Endowment Payout,P0760,Award for Innovation Creative Vision 123539 55065,P0760,Award for Innovation Creative Vision 123539 55065,2026-07-01 13:38:29.229
+69569,Nutrition Program Activity Oteiza 46946,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,69569,Nutrition Program Activity Oteiza 46946,69569,Nutrition Program Activity Oteiza 46946,2026-07-01 13:38:29.229
+16308,CPE CAMPUS PARTNERS RESERVE FUND,1000A,Unrestricted A,1500B,Designated,1600C,Campus Designated Reserves C,1620D,Campus Reserves,16308,CPE CAMPUS PARTNERS RESERVE FUND,16308,CPE CAMPUS PARTNERS RESERVE FUND,2026-07-01 13:38:29.229
+13328,Russell Rustici Rangeland and Cattle Rsch,2000A,Restricted Expendable,2200B,Restricted Gifts B,7000C,Restricted Endowment Income,7700D,Restricted Endowment Income Regents,13328,Russell Rustici Rangeland and Cattle Rsch,13328,Russell Rustici Rangeland and Cattle Rsch,2026-07-01 13:38:29.229
+76732,Metabolic and Cognitive Health Properties Research Fund,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,6300D,Restricted Gift Funds UC Davis,76732,Metabolic and Cognitive Health Properties Research Fund,76732,Metabolic and Cognitive Health Properties Research Fund,2026-07-01 13:38:29.229
+20401,Local Contracts and Grants,2000A,Restricted Expendable,2000B,ContractsandGrants,2040C,State and Local Contracts and Grants C,2040D,State and Local Contracts and Grants D,20401,Local Contracts and Grants,20401,Local Contracts and Grants,2026-07-01 13:38:29.229
+54000,Agency external money 00002,5000A,Agency Funds A,5000B,Agency Funds B,5000C,Agency Funds C,5000D,Agency Funds Campus,54000,Agency external money 00002,54000,Agency external money 00002,2026-07-01 13:38:29.229
+E1521,PRN Barbara J. Van Zandt Endowment Support Fund 325712,2000A,Restricted Expendable,2200B,Restricted Gifts B,6000C,Restricted Expendable Gifts,E000D,Restricted Expendable Gifts Foundation,E1521,PRN Barbara J. Van Zandt Endowment Support Fund 325712,E1521,PRN Barbara J. Van Zandt Endowment Support Fund 325712,2026-07-01 13:38:29.229
diff --git a/server.core/Data/SeedCsv.cs b/server.core/Data/SeedCsv.cs
new file mode 100644
index 0000000..66c334d
--- /dev/null
+++ b/server.core/Data/SeedCsv.cs
@@ -0,0 +1,41 @@
+namespace Server.Core.Data;
+
+///
+/// Reads embedded CSV seed resources (server.core/Data/Seed/*.csv). Rows are split on
+/// commas (the seed exports contain no embedded commas); the header is skipped and
+/// blank lines are ignored.
+///
+public static class SeedCsv
+{
+ public static List ReadRows(string fileName, Func map)
+ {
+ using var stream = OpenResource(fileName);
+ using var reader = new StreamReader(stream);
+ var rows = new List();
+ var isHeader = true;
+ string? line;
+ while ((line = reader.ReadLine()) is not null)
+ {
+ if (isHeader) { isHeader = false; continue; }
+ if (string.IsNullOrWhiteSpace(line)) { continue; }
+ rows.Add(map(line.Split(',')));
+ }
+
+ return rows;
+ }
+
+ public static string? Nullable(string value)
+ {
+ var trimmed = value.Trim();
+ return trimmed.Length == 0 ? null : trimmed;
+ }
+
+ private static Stream OpenResource(string fileName)
+ {
+ var assembly = typeof(SeedCsv).Assembly;
+ var name = assembly.GetManifestResourceNames()
+ .Single(candidate => candidate.EndsWith(fileName, StringComparison.Ordinal));
+ return assembly.GetManifestResourceStream(name)
+ ?? throw new InvalidOperationException($"Embedded seed resource '{fileName}' was not found.");
+ }
+}
diff --git a/server.core/Domain/AccountHierarchy.cs b/server.core/Domain/AccountHierarchy.cs
new file mode 100644
index 0000000..a54784d
--- /dev/null
+++ b/server.core/Domain/AccountHierarchy.cs
@@ -0,0 +1,34 @@
+namespace Server.Core.Domain;
+
+public class AccountHierarchy : ISegmentHierarchy
+{
+ public string Code { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string? ParentLevel0Code { get; set; }
+ public string? ParentLevel0Name { get; set; }
+ public string? ParentLevel1Code { get; set; }
+ public string? ParentLevel1Name { get; set; }
+ public string? ParentLevel2Code { get; set; }
+ public string? ParentLevel2Name { get; set; }
+ public string? ParentLevel3Code { get; set; }
+ public string? ParentLevel3Name { get; set; }
+ public string? ParentLevel4Code { get; set; }
+ public string? ParentLevel4Name { get; set; }
+ public string? ParentLevel5Code { get; set; }
+ public string? ParentLevel5Name { get; set; }
+
+ public IReadOnlyList Levels() =>
+ [
+ .. new (string Level, string? Code, string? Name)[]
+ {
+ ("0", ParentLevel0Code, ParentLevel0Name),
+ ("1", ParentLevel1Code, ParentLevel1Name),
+ ("2", ParentLevel2Code, ParentLevel2Name),
+ ("3", ParentLevel3Code, ParentLevel3Name),
+ ("4", ParentLevel4Code, ParentLevel4Name),
+ ("5", ParentLevel5Code, ParentLevel5Name),
+ }
+ .Where(l => l.Code is not null)
+ .Select(l => new HierarchyLevel(l.Level, l.Code!, l.Name)),
+ ];
+}
diff --git a/server.core/Domain/ActivityHierarchy.cs b/server.core/Domain/ActivityHierarchy.cs
new file mode 100644
index 0000000..7988a95
--- /dev/null
+++ b/server.core/Domain/ActivityHierarchy.cs
@@ -0,0 +1,34 @@
+namespace Server.Core.Domain;
+
+public class ActivityHierarchy : ISegmentHierarchy
+{
+ public string Code { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string? ParentLevel0Code { get; set; }
+ public string? ParentLevel0Name { get; set; }
+ public string? ParentLevel1Code { get; set; }
+ public string? ParentLevel1Name { get; set; }
+ public string? ParentLevel2Code { get; set; }
+ public string? ParentLevel2Name { get; set; }
+ public string? ParentLevel3Code { get; set; }
+ public string? ParentLevel3Name { get; set; }
+ public string? ParentLevel4Code { get; set; }
+ public string? ParentLevel4Name { get; set; }
+ public string? ParentLevel5Code { get; set; }
+ public string? ParentLevel5Name { get; set; }
+
+ public IReadOnlyList Levels() =>
+ [
+ .. new (string Level, string? Code, string? Name)[]
+ {
+ ("0", ParentLevel0Code, ParentLevel0Name),
+ ("1", ParentLevel1Code, ParentLevel1Name),
+ ("2", ParentLevel2Code, ParentLevel2Name),
+ ("3", ParentLevel3Code, ParentLevel3Name),
+ ("4", ParentLevel4Code, ParentLevel4Name),
+ ("5", ParentLevel5Code, ParentLevel5Name),
+ }
+ .Where(l => l.Code is not null)
+ .Select(l => new HierarchyLevel(l.Level, l.Code!, l.Name)),
+ ];
+}
diff --git a/server.core/Domain/ChartStringSegment.cs b/server.core/Domain/ChartStringSegment.cs
new file mode 100644
index 0000000..b57e0f1
--- /dev/null
+++ b/server.core/Domain/ChartStringSegment.cs
@@ -0,0 +1,23 @@
+namespace Server.Core.Domain;
+
+public enum SegmentType
+{
+ FinancialDepartment,
+ Account,
+ Fund,
+ Activity,
+ Ern,
+}
+
+public class ChartStringSegment
+{
+ public SegmentType SegmentType { get; set; }
+
+ public string Code { get; set; } = string.Empty;
+
+ public string? Description { get; set; }
+
+ public bool? IncludeInReport { get; set; }
+
+ public string? Sfn { get; set; }
+}
diff --git a/server.core/Domain/DepartmentHierarchy.cs b/server.core/Domain/DepartmentHierarchy.cs
new file mode 100644
index 0000000..5bb2e67
--- /dev/null
+++ b/server.core/Domain/DepartmentHierarchy.cs
@@ -0,0 +1,37 @@
+namespace Server.Core.Domain;
+
+public class DepartmentHierarchy : ISegmentHierarchy
+{
+ public string Code { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string? ParentLevelACode { get; set; }
+ public string? ParentLevelAName { get; set; }
+ public string? ParentLevelBCode { get; set; }
+ public string? ParentLevelBName { get; set; }
+ public string? ParentLevelCCode { get; set; }
+ public string? ParentLevelCName { get; set; }
+ public string? ParentLevelDCode { get; set; }
+ public string? ParentLevelDName { get; set; }
+ public string? ParentLevelECode { get; set; }
+ public string? ParentLevelEName { get; set; }
+ public string? ParentLevelFCode { get; set; }
+ public string? ParentLevelFName { get; set; }
+ public string? ParentLevelGCode { get; set; }
+ public string? ParentLevelGName { get; set; }
+
+ public IReadOnlyList Levels() =>
+ [
+ .. new (string Level, string? Code, string? Name)[]
+ {
+ ("A", ParentLevelACode, ParentLevelAName),
+ ("B", ParentLevelBCode, ParentLevelBName),
+ ("C", ParentLevelCCode, ParentLevelCName),
+ ("D", ParentLevelDCode, ParentLevelDName),
+ ("E", ParentLevelECode, ParentLevelEName),
+ ("F", ParentLevelFCode, ParentLevelFName),
+ ("G", ParentLevelGCode, ParentLevelGName),
+ }
+ .Where(l => l.Code is not null)
+ .Select(l => new HierarchyLevel(l.Level, l.Code!, l.Name)),
+ ];
+}
diff --git a/server.core/Domain/FundHierarchy.cs b/server.core/Domain/FundHierarchy.cs
new file mode 100644
index 0000000..cde659f
--- /dev/null
+++ b/server.core/Domain/FundHierarchy.cs
@@ -0,0 +1,34 @@
+namespace Server.Core.Domain;
+
+public class FundHierarchy : ISegmentHierarchy
+{
+ public string Code { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string? ParentLevel0Code { get; set; }
+ public string? ParentLevel0Name { get; set; }
+ public string? ParentLevel1Code { get; set; }
+ public string? ParentLevel1Name { get; set; }
+ public string? ParentLevel2Code { get; set; }
+ public string? ParentLevel2Name { get; set; }
+ public string? ParentLevel3Code { get; set; }
+ public string? ParentLevel3Name { get; set; }
+ public string? ParentLevel4Code { get; set; }
+ public string? ParentLevel4Name { get; set; }
+ public string? ParentLevel5Code { get; set; }
+ public string? ParentLevel5Name { get; set; }
+
+ public IReadOnlyList Levels() =>
+ [
+ .. new (string Level, string? Code, string? Name)[]
+ {
+ ("0", ParentLevel0Code, ParentLevel0Name),
+ ("1", ParentLevel1Code, ParentLevel1Name),
+ ("2", ParentLevel2Code, ParentLevel2Name),
+ ("3", ParentLevel3Code, ParentLevel3Name),
+ ("4", ParentLevel4Code, ParentLevel4Name),
+ ("5", ParentLevel5Code, ParentLevel5Name),
+ }
+ .Where(l => l.Code is not null)
+ .Select(l => new HierarchyLevel(l.Level, l.Code!, l.Name)),
+ ];
+}
diff --git a/server.core/Domain/SegmentHierarchy.cs b/server.core/Domain/SegmentHierarchy.cs
new file mode 100644
index 0000000..431c1d3
--- /dev/null
+++ b/server.core/Domain/SegmentHierarchy.cs
@@ -0,0 +1,10 @@
+namespace Server.Core.Domain;
+
+public readonly record struct HierarchyLevel(string Level, string Code, string? Name);
+
+public interface ISegmentHierarchy
+{
+ string Code { get; }
+ string? Description { get; }
+ IReadOnlyList Levels();
+}
diff --git a/server.core/server.core.csproj b/server.core/server.core.csproj
index 5cf7b1a..e1416c7 100644
--- a/server.core/server.core.csproj
+++ b/server.core/server.core.csproj
@@ -11,6 +11,10 @@
+
+
+
+
diff --git a/server/Controllers/ChartStringSegmentsController.cs b/server/Controllers/ChartStringSegmentsController.cs
new file mode 100644
index 0000000..03eb6dd
--- /dev/null
+++ b/server/Controllers/ChartStringSegmentsController.cs
@@ -0,0 +1,98 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Data;
+using Server.Core.Domain;
+using Server.Models.ChartStringSegments;
+
+namespace Server.Controllers;
+
+public class ChartStringSegmentsController : ApiControllerBase
+{
+ private readonly AppDbContext _db;
+
+ public ChartStringSegmentsController(AppDbContext db)
+ {
+ _db = db;
+ }
+
+ // GET api/chartstringsegments
+ [HttpGet]
+ public async Task>> Get(CancellationToken cancellationToken)
+ {
+ var segments = await _db.ChartStringSegments.ToListAsync(cancellationToken);
+
+ var departments = await _db.DepartmentHierarchies.ToDictionaryAsync(h => h.Code, cancellationToken);
+ var accounts = await _db.AccountHierarchies.ToDictionaryAsync(h => h.Code, cancellationToken);
+ var funds = await _db.FundHierarchies.ToDictionaryAsync(h => h.Code, cancellationToken);
+ var activities = await _db.ActivityHierarchies.ToDictionaryAsync(h => h.Code, cancellationToken);
+
+ IReadOnlyList HierarchyFor(SegmentType type, string code)
+ {
+ ISegmentHierarchy? source = type switch
+ {
+ SegmentType.FinancialDepartment => departments.GetValueOrDefault(code),
+ SegmentType.Account => accounts.GetValueOrDefault(code),
+ SegmentType.Fund => funds.GetValueOrDefault(code),
+ SegmentType.Activity => activities.GetValueOrDefault(code),
+ _ => null,
+ };
+
+ return source is null
+ ? []
+ : source.Levels().Select(l => new HierarchyLevelDto(l.Level, l.Code, l.Name)).ToList();
+ }
+
+ var dtos = segments
+ .OrderBy(segment => segment.SegmentType)
+ .ThenBy(segment => segment.Code)
+ .Select(segment => new ChartStringSegmentDto(
+ segment.SegmentType.ToString(),
+ segment.Code,
+ segment.Description,
+ segment.IncludeInReport,
+ segment.Sfn,
+ HierarchyFor(segment.SegmentType, segment.Code)))
+ .ToList();
+
+ return Ok(dtos);
+ }
+
+ // PATCH api/chartstringsegments
+ [HttpPatch]
+ public async Task UpdateClassification(
+ [FromBody] UpdateClassificationRequest request,
+ CancellationToken cancellationToken)
+ {
+ if (!Enum.TryParse(request.SegmentType, out var segmentType))
+ {
+ return BadRequest($"Unknown segment type '{request.SegmentType}'.");
+ }
+
+ var segment = await _db.ChartStringSegments.FirstOrDefaultAsync(
+ candidate => candidate.SegmentType == segmentType && candidate.Code == request.Code,
+ cancellationToken);
+
+ if (segment is null)
+ {
+ return NotFound();
+ }
+
+ var isFund = segmentType == SegmentType.Fund;
+
+ if (!isFund && request.Sfn is not null)
+ {
+ return BadRequest("SFN is only valid for Fund segments.");
+ }
+
+ if (isFund && request.IncludeInReport == true && !FundSfns.IsValidForInclusion(request.Sfn))
+ {
+ return BadRequest($"Invalid SFN '{request.Sfn}' for an included fund.");
+ }
+
+ segment.IncludeInReport = request.IncludeInReport;
+ segment.Sfn = isFund && request.IncludeInReport == true ? request.Sfn : null;
+ await _db.SaveChangesAsync(cancellationToken);
+
+ return NoContent();
+ }
+}
diff --git a/server/Models/ChartStringSegments/ChartStringSegmentDtos.cs b/server/Models/ChartStringSegments/ChartStringSegmentDtos.cs
new file mode 100644
index 0000000..433bc49
--- /dev/null
+++ b/server/Models/ChartStringSegments/ChartStringSegmentDtos.cs
@@ -0,0 +1,17 @@
+namespace Server.Models.ChartStringSegments;
+
+public sealed record ChartStringSegmentDto(
+ string SegmentType,
+ string Code,
+ string? Description,
+ bool? IncludeInReport,
+ string? Sfn,
+ IReadOnlyList Hierarchy);
+
+public sealed record HierarchyLevelDto(string Level, string Code, string? Name);
+
+public sealed record UpdateClassificationRequest(
+ string SegmentType,
+ string Code,
+ bool? IncludeInReport,
+ string? Sfn);
diff --git a/server/Models/ChartStringSegments/FundSfns.cs b/server/Models/ChartStringSegments/FundSfns.cs
new file mode 100644
index 0000000..7bdb94a
--- /dev/null
+++ b/server/Models/ChartStringSegments/FundSfns.cs
@@ -0,0 +1,12 @@
+namespace Server.Models.ChartStringSegments;
+
+public static class FundSfns
+{
+ public const string MultipleMarker = "Multiple";
+
+ public static readonly IReadOnlySet Codes =
+ new HashSet { "201", "202", "203", "205", "220", "221", "223" };
+
+ public static bool IsValidForInclusion(string? sfn) =>
+ sfn is not null && (Codes.Contains(sfn) || sfn == MultipleMarker);
+}
diff --git a/tests/server.tests/ChartStringSegments/ChartStringSegmentMappingTests.cs b/tests/server.tests/ChartStringSegments/ChartStringSegmentMappingTests.cs
new file mode 100644
index 0000000..7b3b1d9
--- /dev/null
+++ b/tests/server.tests/ChartStringSegments/ChartStringSegmentMappingTests.cs
@@ -0,0 +1,45 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Data;
+using Server.Core.Domain;
+
+namespace Server.Tests.ChartStringSegments;
+
+public class ChartStringSegmentMappingTests
+{
+ [Fact]
+ public void Maps_to_data_schema_with_composite_key()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ var entityType = db.Model.FindEntityType(typeof(ChartStringSegment));
+
+ entityType.Should().NotBeNull();
+ entityType!.GetSchema().Should().Be("data");
+ entityType.GetTableName().Should().Be("ChartStringSegments");
+ entityType.FindPrimaryKey()!.Properties.Select(p => p.Name)
+ .Should().Equal(nameof(ChartStringSegment.SegmentType), nameof(ChartStringSegment.Code));
+ }
+
+ [Fact]
+ public void Stores_segment_type_as_string()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ var property = db.Model.FindEntityType(typeof(ChartStringSegment))!
+ .FindProperty(nameof(ChartStringSegment.SegmentType));
+
+ property!.GetProviderClrType().Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void Sfn_column_allows_ten_characters()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ var property = db.Model.FindEntityType(typeof(ChartStringSegment))!
+ .FindProperty(nameof(ChartStringSegment.Sfn));
+
+ property!.GetMaxLength().Should().Be(10);
+ }
+}
diff --git a/tests/server.tests/ChartStringSegments/ChartStringSegmentSeedTests.cs b/tests/server.tests/ChartStringSegments/ChartStringSegmentSeedTests.cs
new file mode 100644
index 0000000..4db68c0
--- /dev/null
+++ b/tests/server.tests/ChartStringSegments/ChartStringSegmentSeedTests.cs
@@ -0,0 +1,79 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Data;
+using Server.Core.Domain;
+
+namespace Server.Tests.ChartStringSegments;
+
+public class ChartStringSegmentSeedTests
+{
+ [Fact]
+ public async Task EnsureSeeded_derives_one_segment_per_hierarchy_row()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ await ChartStringSegmentSeed.EnsureSeededAsync(db);
+
+ var expected =
+ await db.AccountHierarchies.CountAsync() +
+ await db.FundHierarchies.CountAsync() +
+ await db.ActivityHierarchies.CountAsync() +
+ await db.DepartmentHierarchies.CountAsync() +
+ db.ChartStringSegments.Count(segment => segment.SegmentType == SegmentType.Ern);
+ (await db.ChartStringSegments.CountAsync()).Should().Be(expected);
+ }
+
+ [Fact]
+ public async Task EnsureSeeded_loads_ern_codes_from_csv()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ await ChartStringSegmentSeed.EnsureSeededAsync(db);
+
+ var ern = db.ChartStringSegments
+ .Where(segment => segment.SegmentType == SegmentType.Ern)
+ .ToList();
+ ern.Count.Should().BeGreaterThan(200);
+
+ // REG (Regular Pay) has IncludeInAD419FTE = true and is not in the first 10 by code.
+ var reg = ern.Single(segment => segment.Code == "REG");
+ reg.IncludeInReport.Should().BeTrue();
+ reg.Description.Should().Be("Regular Pay");
+
+ // The first 10 by ordinal DOS code are left unset for demo.
+ ern.OrderBy(segment => segment.Code, StringComparer.Ordinal)
+ .Take(10)
+ .All(segment => segment.IncludeInReport == null)
+ .Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task EnsureSeeded_maps_segment_types_to_their_hierarchy()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ await ChartStringSegmentSeed.EnsureSeededAsync(db);
+
+ // Account 500000 comes from AccountHierarchy, so it is an Account segment.
+ var account = await db.ChartStringSegments.FindAsync(SegmentType.Account, "500000");
+ account.Should().NotBeNull();
+ // Fund 71549 comes from FundHierarchy.
+ (await db.ChartStringSegments.FindAsync(SegmentType.Fund, "71549")).Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task EnsureSeeded_is_idempotent()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ await ChartStringSegmentSeed.EnsureSeededAsync(db);
+ var count = await db.ChartStringSegments.CountAsync();
+ await ChartStringSegmentSeed.EnsureSeededAsync(db);
+
+ (await db.ChartStringSegments.CountAsync()).Should().Be(count);
+ }
+}
diff --git a/tests/server.tests/ChartStringSegments/ChartStringSegmentsControllerTests.cs b/tests/server.tests/ChartStringSegments/ChartStringSegmentsControllerTests.cs
new file mode 100644
index 0000000..5046a72
--- /dev/null
+++ b/tests/server.tests/ChartStringSegments/ChartStringSegmentsControllerTests.cs
@@ -0,0 +1,235 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc;
+using Server.Controllers;
+using Server.Core.Domain;
+using Server.Models.ChartStringSegments;
+
+namespace Server.Tests.ChartStringSegments;
+
+public class ChartStringSegmentsControllerTests
+{
+ [Fact]
+ public async Task Get_returns_all_segments()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.AddRange(
+ new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "45530", Description = "AES", IncludeInReport = true, Sfn = "220" },
+ new ChartStringSegment { SegmentType = SegmentType.Account, Code = "500000", Description = "S and E", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.Get(CancellationToken.None);
+
+ var ok = result.Result.Should().BeOfType().Subject;
+ ok.Value.Should().BeAssignableTo>()
+ .Which.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public async Task Get_includes_hierarchy_for_segment_with_matching_code()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "45530", IncludeInReport = true, Sfn = "220" });
+ db.FundHierarchies.Add(new FundHierarchy
+ {
+ Code = "45530",
+ ParentLevel0Code = "STATE", ParentLevel0Name = "State Funds",
+ ParentLevel1Code = "APPROP", ParentLevel1Name = "Appropriations",
+ });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.Get(CancellationToken.None);
+
+ var ok = result.Result.Should().BeOfType().Subject;
+ var dto = ok.Value.Should().BeAssignableTo>().Subject.Single();
+ dto.Hierarchy.Should().Equal(
+ new HierarchyLevelDto("0", "STATE", "State Funds"),
+ new HierarchyLevelDto("1", "APPROP", "Appropriations"));
+ }
+
+ [Theory]
+ [InlineData(SegmentType.FinancialDepartment, "A")]
+ [InlineData(SegmentType.Account, "0")]
+ [InlineData(SegmentType.Fund, "0")]
+ [InlineData(SegmentType.Activity, "0")]
+ public async Task Get_reads_hierarchy_from_the_matching_segment_types_own_table(
+ SegmentType segmentType, string expectedLevel)
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ const string code = "12345";
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = segmentType, Code = code, IncludeInReport = null });
+
+ switch (segmentType)
+ {
+ case SegmentType.FinancialDepartment:
+ db.DepartmentHierarchies.Add(new DepartmentHierarchy
+ {
+ Code = code, ParentLevelACode = "X", ParentLevelAName = "XName",
+ });
+ break;
+ case SegmentType.Account:
+ db.AccountHierarchies.Add(new AccountHierarchy
+ {
+ Code = code, ParentLevel0Code = "X", ParentLevel0Name = "XName",
+ });
+ break;
+ case SegmentType.Fund:
+ db.FundHierarchies.Add(new FundHierarchy
+ {
+ Code = code, ParentLevel0Code = "X", ParentLevel0Name = "XName",
+ });
+ break;
+ case SegmentType.Activity:
+ db.ActivityHierarchies.Add(new ActivityHierarchy
+ {
+ Code = code, ParentLevel0Code = "X", ParentLevel0Name = "XName",
+ });
+ break;
+ }
+
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.Get(CancellationToken.None);
+
+ var ok = result.Result.Should().BeOfType().Subject;
+ var dto = ok.Value.Should().BeAssignableTo>().Subject.Single();
+ dto.Hierarchy.Should().Equal(new HierarchyLevelDto(expectedLevel, "X", "XName"));
+ }
+
+ [Fact]
+ public async Task Get_returns_empty_hierarchy_when_no_matching_row()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Account, Code = "500000", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.Get(CancellationToken.None);
+
+ var ok = result.Result.Should().BeOfType().Subject;
+ var dto = ok.Value.Should().BeAssignableTo>().Subject.Single();
+ dto.Hierarchy.Should().BeEmpty();
+ }
+
+ [Fact]
+ public async Task Patch_updates_include_flag()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "70575", IncludeInReport = null, Sfn = "219" });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "70575", true, "201"), CancellationToken.None);
+
+ result.Should().BeOfType();
+ var updated = await db.ChartStringSegments.FindAsync(SegmentType.Fund, "70575");
+ updated!.IncludeInReport.Should().BeTrue();
+ updated.Sfn.Should().Be("201");
+ }
+
+ [Fact]
+ public async Task Patch_returns_not_found_for_missing_segment()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "00000", false, null), CancellationToken.None);
+
+ result.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task Patch_returns_bad_request_for_unknown_segment_type()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Nonsense", "00000", false, null), CancellationToken.None);
+
+ result.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task Patch_sets_sfn_for_included_fund()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "70575", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "70575", true, "220"), CancellationToken.None);
+
+ result.Should().BeOfType();
+ var updated = await db.ChartStringSegments.FindAsync(SegmentType.Fund, "70575");
+ updated!.IncludeInReport.Should().BeTrue();
+ updated.Sfn.Should().Be("220");
+ }
+
+ [Fact]
+ public async Task Patch_clears_sfn_when_fund_excluded()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "45530", IncludeInReport = true, Sfn = "220" });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "45530", false, null), CancellationToken.None);
+
+ result.Should().BeOfType();
+ var updated = await db.ChartStringSegments.FindAsync(SegmentType.Fund, "45530");
+ updated!.IncludeInReport.Should().BeFalse();
+ updated.Sfn.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task Patch_rejects_invalid_sfn_for_included_fund()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "70575", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "70575", true, "999"), CancellationToken.None);
+
+ result.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task Patch_rejects_sfn_on_non_fund_segment()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Account, Code = "500000", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Account", "500000", true, "201"), CancellationToken.None);
+
+ result.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task Patch_accepts_multiple_marker_for_included_fund()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ db.ChartStringSegments.Add(new ChartStringSegment { SegmentType = SegmentType.Fund, Code = "70575", IncludeInReport = null });
+ await db.SaveChangesAsync();
+ var controller = new ChartStringSegmentsController(db);
+
+ var result = await controller.UpdateClassification(
+ new UpdateClassificationRequest("Fund", "70575", true, "Multiple"), CancellationToken.None);
+
+ result.Should().BeOfType();
+ var updated = await db.ChartStringSegments.FindAsync(SegmentType.Fund, "70575");
+ updated!.IncludeInReport.Should().BeTrue();
+ updated.Sfn.Should().Be("Multiple");
+ }
+}
diff --git a/tests/server.tests/ChartStringSegments/HierarchyMappingTests.cs b/tests/server.tests/ChartStringSegments/HierarchyMappingTests.cs
new file mode 100644
index 0000000..9e52b9d
--- /dev/null
+++ b/tests/server.tests/ChartStringSegments/HierarchyMappingTests.cs
@@ -0,0 +1,54 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Domain;
+
+namespace Server.Tests.ChartStringSegments;
+
+public class HierarchyMappingTests
+{
+ [Theory]
+ [InlineData(typeof(DepartmentHierarchy), "DepartmentHierarchy")]
+ [InlineData(typeof(AccountHierarchy), "AccountHierarchy")]
+ [InlineData(typeof(FundHierarchy), "FundHierarchy")]
+ [InlineData(typeof(ActivityHierarchy), "ActivityHierarchy")]
+ public void Maps_to_data_schema_keyed_by_code(Type clr, string table)
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ var entityType = db.Model.FindEntityType(clr);
+
+ entityType.Should().NotBeNull();
+ entityType!.GetSchema().Should().Be("data");
+ entityType.GetTableName().Should().Be(table);
+ entityType.FindPrimaryKey()!.Properties.Select(p => p.Name).Should().Equal("Code");
+ }
+
+ [Fact]
+ public void Department_levels_return_ordered_non_null_pairs()
+ {
+ var dept = new DepartmentHierarchy
+ {
+ Code = "031000",
+ Description = "Plant Sciences",
+ ParentLevelACode = "CAES", ParentLevelAName = "Ag & Env Sciences",
+ ParentLevelBCode = "DIV1", ParentLevelBName = "Division One",
+ };
+
+ dept.Levels().Should().Equal(
+ new HierarchyLevel("A", "CAES", "Ag & Env Sciences"),
+ new HierarchyLevel("B", "DIV1", "Division One"));
+ }
+
+ [Fact]
+ public void Fund_level_properties_have_max_lengths()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+ var entity = db.Model.FindEntityType(typeof(FundHierarchy))!;
+
+ entity.FindProperty("ParentLevel0Code")!.GetMaxLength().Should().Be(20);
+ entity.FindProperty("ParentLevel0Name")!.GetMaxLength().Should().Be(1000);
+ entity.FindProperty("Description")!.GetMaxLength().Should().Be(1000);
+ // Code matches ChartStringSegment.Code (NVARCHAR(50)) so joins on Code align.
+ entity.FindProperty("Code")!.GetMaxLength().Should().Be(50);
+ }
+}
diff --git a/tests/server.tests/ChartStringSegments/HierarchySeedTests.cs b/tests/server.tests/ChartStringSegments/HierarchySeedTests.cs
new file mode 100644
index 0000000..86d549f
--- /dev/null
+++ b/tests/server.tests/ChartStringSegments/HierarchySeedTests.cs
@@ -0,0 +1,52 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Server.Core.Data;
+using Server.Core.Domain;
+
+namespace Server.Tests.ChartStringSegments;
+
+public class HierarchySeedTests
+{
+ [Fact]
+ public async Task EnsureSeeded_loads_rows_from_the_embedded_csvs()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ (await db.AccountHierarchies.CountAsync()).Should().BeGreaterThan(100);
+ (await db.FundHierarchies.CountAsync()).Should().BeGreaterThan(50);
+ (await db.ActivityHierarchies.CountAsync()).Should().BeGreaterThan(20);
+ (await db.AccountHierarchies.FindAsync("500000")).Should().NotBeNull();
+ (await db.FundHierarchies.FindAsync("71549")).Should().NotBeNull();
+ (await db.ActivityHierarchies.FindAsync("202042")).Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task EnsureSeeded_synthesizes_department_levels_a_through_g()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ var department = await db.DepartmentHierarchies.FindAsync("ANUT006");
+ department.Should().NotBeNull();
+ // Fabricated top levels plus the real D-G chain from the source file.
+ department!.Levels().Select(level => level.Level)
+ .Should().Equal("A", "B", "C", "D", "E", "F", "G");
+ department.ParentLevelACode.Should().Be("UCD");
+ department.ParentLevelDCode.Should().Be("ACL100D");
+ }
+
+ [Fact]
+ public async Task EnsureSeeded_is_idempotent()
+ {
+ using var db = TestDbContextFactory.CreateInMemory();
+
+ await HierarchySeed.EnsureSeededAsync(db);
+ var count = await db.FundHierarchies.CountAsync();
+ await HierarchySeed.EnsureSeededAsync(db);
+
+ (await db.FundHierarchies.CountAsync()).Should().Be(count);
+ }
+}