diff --git a/PR_diff_base-qa_to_head-qa.diff b/PR_diff_base-qa_to_head-qa.diff new file mode 100644 index 000000000..c6c988a39 --- /dev/null +++ b/PR_diff_base-qa_to_head-qa.diff @@ -0,0 +1,672 @@ +diff --git a/src/app/file-browser/components/edit-date-time-modal/edit-date-time-modal.component.scss b/src/app/file-browser/components/edit-date-time-modal/edit-date-time-modal.component.scss +index 628a98be..78fc1427 100644 +--- a/src/app/file-browser/components/edit-date-time-modal/edit-date-time-modal.component.scss ++++ b/src/app/file-browser/components/edit-date-time-modal/edit-date-time-modal.component.scss +@@ -251,13 +251,7 @@ + } + + .pr-edtf-error { +- font-size: 12px; +- line-height: 16px; +- color: $red; +- word-wrap: break-word; +- overflow-wrap: break-word; +- white-space: normal; +- min-width: 0; ++ @include edtf-error-message; + } + } + +diff --git a/src/app/file-browser/components/file-viewer/file-viewer.component.html b/src/app/file-browser/components/file-viewer/file-viewer.component.html +index c441a243..ba24ee97 100644 +--- a/src/app/file-browser/components/file-viewer/file-viewer.component.html ++++ b/src/app/file-browser/components/file-viewer/file-viewer.component.html +@@ -125,26 +125,38 @@ + > + } + ++ @if (showEdtfDatePicker) { ++
++ ++
++ } + +- +- +- +- ++ @if (!showEdtfDatePicker) { ++ ++ ++ ++ ++ } + + + +diff --git a/src/app/file-browser/components/file-viewer/file-viewer.component.spec.ts b/src/app/file-browser/components/file-viewer/file-viewer.component.spec.ts +index d06c6281..f8e3f8db 100644 +--- a/src/app/file-browser/components/file-viewer/file-viewer.component.spec.ts ++++ b/src/app/file-browser/components/file-viewer/file-viewer.component.spec.ts +@@ -16,7 +16,13 @@ import { FeatureFlagService } from '@root/app/feature-flag/services/feature-flag + import { MockComponent } from 'ng-mocks'; + import { GetThumbnailPipe } from '@shared/pipes/get-thumbnail.pipe'; + import { environment } from '@root/environments/environment'; ++import { MessageService } from '@shared/services/message/message.service'; ++import { ++ DateTimeModel, ++ EdtfService, ++} from '@shared/services/edtf-service/edtf.service'; + import { TagsComponent } from '../../../shared/components/tags/tags.component'; ++import { EditDateTimeModalService } from '../edit-date-time-modal/edit-date-time-modal.service'; + import { FileViewerComponent } from './file-viewer.component'; + + @Pipe({ name: 'dsFileSize', standalone: false }) +@@ -238,6 +244,19 @@ describe('FileViewerComponent', () => { + isEnabled: (flag: string) => featureFlagsEnabled.get(flag) ?? false, + }, + }, ++ { ++ provide: MessageService, ++ useValue: { ++ showError: () => {}, ++ showMessage: () => {}, ++ }, ++ }, ++ { ++ provide: EditDateTimeModalService, ++ useValue: { ++ open: () => ({ closed: { subscribe: () => {} } }), ++ }, ++ }, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }).compileComponents(); +@@ -253,6 +272,163 @@ describe('FileViewerComponent', () => { + expect(component).not.toBeNull(); + }); + ++ describe('edtf-date feature flag', () => { ++ it('should show the EDTF date picker when the edtf-date flag is enabled', async () => { ++ featureFlagsEnabled.set('edtf-date', true); ++ await recreateComponent(); ++ ++ expect(component.showEdtfDatePicker).toBe(true); ++ expect( ++ fixture.nativeElement.querySelector('pr-sidebar-date-picker'), ++ ).toBeTruthy(); ++ }); ++ ++ it('should show the legacy date field and hide the EDTF picker when the edtf-date flag is disabled', async () => { ++ featureFlagsEnabled.set('edtf-date', false); ++ await recreateComponent(); ++ ++ expect(component.showEdtfDatePicker).toBe(false); ++ expect( ++ fixture.nativeElement.querySelector('pr-sidebar-date-picker'), ++ ).toBeNull(); ++ ++ const dateRowLabel = Array.from( ++ fixture.nativeElement.querySelectorAll('.metadata-table td'), ++ ).find((td: HTMLElement) => td.textContent?.trim() === 'Date'); ++ ++ expect(dateRowLabel).toBeTruthy(); ++ }); ++ }); ++ ++ describe('EDTF date handling', () => { ++ const recordWithDate = () => ++ new RecordVO({ ++ type: 'document', ++ displayName: 'Dated Doc', ++ TagVOs: [], ++ displayTime: '1985-05-20', ++ }); ++ ++ beforeEach(() => { ++ featureFlagsEnabled.set('edtf-date', true); ++ }); ++ ++ it('should not parse the date or show an error when the edtf-date flag is disabled', async () => { ++ featureFlagsEnabled.set('edtf-date', false); ++ activatedRouteData.currentRecord = new RecordVO({ ++ type: 'document', ++ displayName: 'Invalid Date Doc', ++ TagVOs: [], ++ displayTime: 'not-a-valid-edtf-date', ++ }); ++ const showErrorSpy = spyOn(TestBed.inject(MessageService), 'showError'); ++ await recreateComponent(); ++ ++ expect(component.displayTimeObject).toBeNull(); ++ expect(showErrorSpy).not.toHaveBeenCalled(); ++ }); ++ ++ it('should compute the cached display time from the record on init', async () => { ++ activatedRouteData.currentRecord = recordWithDate(); ++ await recreateComponent(); ++ ++ expect(component.displayTimeObject?.date.year).toBe('1985'); ++ }); ++ ++ it('should reset the cached display time and show one error when an invalid date is saved', async () => { ++ activatedRouteData.currentRecord = recordWithDate(); ++ await recreateComponent(); ++ ++ const edtfService = TestBed.inject(EdtfService); ++ spyOn(edtfService, 'toEdtfDate').and.throwError('invalid date'); ++ const showErrorSpy = spyOn(TestBed.inject(MessageService), 'showError'); ++ ++ await component.onDateSaved({ ++ date: { year: 'bad' } as never, ++ time: { format: 'am' }, ++ } as DateTimeModel); ++ ++ expect(showErrorSpy).toHaveBeenCalledTimes(1); ++ expect(component.displayTimeObject?.date.year).toBe('1985'); ++ }); ++ ++ it('should save null when the date is cleared to empty', async () => { ++ activatedRouteData.currentRecord = recordWithDate(); ++ await recreateComponent(); ++ ++ await component.onDateSaved({ ++ date: { year: '', month: '', day: '' }, ++ time: { format: 'am' }, ++ }); ++ ++ expect(savedProperty).toEqual({ name: 'displayTime', value: null }); ++ }); ++ ++ it('should save the EDTF string unchanged for a non-empty date', async () => { ++ activatedRouteData.currentRecord = recordWithDate(); ++ await recreateComponent(); ++ ++ await component.onDateSaved({ ++ date: { year: '1990', month: '06', day: '15' }, ++ time: { format: 'am' }, ++ }); ++ ++ expect(savedProperty).toEqual({ ++ name: 'displayTime', ++ value: '1990-06-15', ++ }); ++ }); ++ ++ it('should re-sync the picker to the reverted value after a failed backend save', async () => { ++ activatedRouteData.currentRecord = recordWithDate(); ++ await recreateComponent(); ++ ++ // Mimic EditService on a server failure: optimistic update now, ++ // revert on a later macrotask. The re-sync must wait for this. ++ spyOn(TestBed.inject(EditService), 'saveItemVoProperty').and.callFake( ++ async (item, _property, value) => { ++ item.displayTime = value; ++ await new Promise((resolve) => { ++ setTimeout(resolve); ++ }); ++ item.displayTime = '1985-05-20'; ++ }, ++ ); ++ ++ await component.onDateSaved({ ++ date: { year: '1990', month: '06', day: '15' }, ++ time: { format: 'am' }, ++ }); ++ ++ expect(component.displayTimeObject?.date.year).toBe('1985'); ++ }); ++ ++ it('should show an empty date when displayTime is explicitly null, ignoring displayDT', async () => { ++ activatedRouteData.currentRecord = new RecordVO({ ++ type: 'document', ++ displayName: 'Cleared Doc', ++ TagVOs: [], ++ displayTime: null, ++ displayDT: '1985-05-20T00:00:00Z', ++ }); ++ await recreateComponent(); ++ ++ expect(component.displayTimeObject).toBeNull(); ++ }); ++ ++ it('should fall back to displayDT when displayTime is undefined', async () => { ++ activatedRouteData.currentRecord = new RecordVO({ ++ type: 'document', ++ displayName: 'Legacy Doc', ++ TagVOs: [], ++ displayDT: '1985-05-20T00:00:00Z', ++ }); ++ await recreateComponent(); ++ ++ expect(component.displayTimeObject?.date.year).toBe('1985'); ++ }); ++ }); ++ + it('should have two tags components', () => { + const tagsComponents = fixture.nativeElement.querySelectorAll('pr-tags'); + +diff --git a/src/app/file-browser/components/file-viewer/file-viewer.component.ts b/src/app/file-browser/components/file-viewer/file-viewer.component.ts +index f3a9e136..ea5a2a0a 100644 +--- a/src/app/file-browser/components/file-viewer/file-viewer.component.ts ++++ b/src/app/file-browser/components/file-viewer/file-viewer.component.ts +@@ -31,7 +31,13 @@ import { ShareLinksService } from '@root/app/share-links/services/share-links.se + import { ApiService } from '@shared/services/api/api.service'; + import { FeatureFlagService } from '@root/app/feature-flag/services/feature-flag.service'; + import { environment } from '@root/environments/environment'; ++import { ++ DateTimeModel, ++ EdtfService, ++} from '@shared/services/edtf-service/edtf.service'; ++import { MessageService } from '@shared/services/message/message.service'; + import { TagsService } from '../../../core/services/tags/tags.service'; ++import { EditDateTimeModalService } from '../edit-date-time-modal/edit-date-time-modal.service'; + + @Component({ + selector: 'pr-file-viewer', +@@ -63,6 +69,12 @@ export class FileViewerComponent implements OnInit, OnDestroy { + + public canEdit: boolean; + ++ public showEdtfDatePicker = false; ++ ++ public editingDate: boolean = false; ++ ++ public displayTimeObject: DateTimeModel | null = null; ++ + // Swiping + private touchElement: HTMLElement; + private thumbElement: HTMLElement; +@@ -78,10 +90,10 @@ export class FileViewerComponent implements OnInit, OnDestroy { + + // UI + public useMinimalView = false; +- public editingDate: boolean = false; + private bodyScrollTop: number; + private itemTagsSubscription: Subscription; + private tagsSubscription: Subscription; ++ private dateModalSubscription?: Subscription; + private isUnlistedShare = true; + + constructor( +@@ -89,6 +101,7 @@ export class FileViewerComponent implements OnInit, OnDestroy { + private route: ActivatedRoute, + private element: ElementRef, + private dataService: DataService, ++ private message: MessageService, + @Inject(DOCUMENT) private document: any, + public sanitizer: DomSanitizer, + private accountService: AccountService, +@@ -98,10 +111,14 @@ export class FileViewerComponent implements OnInit, OnDestroy { + private shareLinksService: ShareLinksService, + private api: ApiService, + private feature: FeatureFlagService, ++ private edtfService: EdtfService, ++ private editDateTimeModalService: EditDateTimeModalService, + ) { + // store current scroll position in file list + this.bodyScrollTop = window.scrollY; + ++ this.showEdtfDatePicker = this.feature.isEnabled('edtf-date'); ++ + const resolvedRecord = route.snapshot.data.currentRecord; + this.allTags = tagsService.getTags(); + +@@ -187,6 +204,7 @@ export class FileViewerComponent implements OnInit, OnDestroy { + }); + this.itemTagsSubscription.unsubscribe(); + this.tagsSubscription.unsubscribe(); ++ this.dateModalSubscription?.unsubscribe(); + } + + private setRecordsToPreview(resolvedRecord: RecordVO) { +@@ -245,6 +263,7 @@ export class FileViewerComponent implements OnInit, OnDestroy { + this.replayUrl = this.getReplayUrl(); + } + this.setCurrentTags(); ++ this.updateDisplayTimeObject(); + } + + toggleSwipe(value: boolean) { +@@ -433,11 +452,62 @@ export class FileViewerComponent implements OnInit, OnDestroy { + } + } + ++ private updateDisplayTimeObject(): void { ++ if (!this.showEdtfDatePicker) { ++ return; ++ } ++ const record = this.currentRecord; ++ const hasExplicitlyClearedDate = record?.displayTime === null; ++ const timeSource = hasExplicitlyClearedDate ++ ? null ++ : record?.displayTime || record?.displayDT; ++ try { ++ this.displayTimeObject = timeSource ++ ? this.edtfService.toDateTimeModel(timeSource) ++ : null; ++ } catch (err) { ++ this.displayTimeObject = null; ++ this.message.showError({ message: err?.message }); ++ } ++ } ++ ++ public async onDateSaved(result: DateTimeModel): Promise { ++ await this.saveDisplayTime(result); ++ } ++ ++ public async onDateMoreOptions(modalData: DateTimeModel): Promise { ++ const dialogRef = this.editDateTimeModalService.open(modalData); ++ ++ this.dateModalSubscription = dialogRef.closed.subscribe(async (result) => { ++ if (result) { ++ await this.saveDisplayTime(result); ++ } ++ }); ++ } ++ ++ private async saveDisplayTime(result: DateTimeModel): Promise { ++ try { ++ const edtfDate = this.edtfService.toEdtfDate(result); ++ const newDisplayTime = edtfDate === '' ? null : edtfDate; ++ await this.onFinishEditing('displayTime', newDisplayTime); ++ } catch (err) { ++ this.message.showError({ message: err?.message }); ++ } finally { ++ // Recompute so the picker re-syncs to the stored value, whether the ++ // save came from the inline picker or the modal, and on failure too. ++ this.updateDisplayTimeObject(); ++ } ++ } ++ ++ public onDateToggle(active: boolean): void { ++ this.editingDate = active; ++ } ++ + public async onFinishEditing( + property: KeysOfType, + value: string, + ): Promise { +- this.editService.saveItemVoProperty( ++ await this.editService.saveItemVoProperty( + this.currentRecord as ItemVO, + property, + value, +@@ -456,10 +526,6 @@ export class FileViewerComponent implements OnInit, OnDestroy { + } + } + +- public onDateToggle(active: boolean): void { +- this.editingDate = active; +- } +- + public onDownloadClick(): void { + this.dataService.downloadFile(this.currentRecord); + } +diff --git a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.html b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.html +index 9587f223..8243aa30 100644 +--- a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.html ++++ b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.html +@@ -77,6 +77,10 @@ + + + ++ @if (!isEdtfValid()) { ++ {{ edtfErrorMessage() }} ++ } ++ + +diff --git a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.scss b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.scss +index 5eae2c1b..bbba49a0 100644 +--- a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.scss ++++ b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.scss +@@ -147,6 +147,12 @@ + } + } + ++ .pr-edtf-error { ++ @include edtf-error-message; ++ display: block; ++ padding: 4px 16px; ++ } ++ + .pr-sidebar-date-picker-footer { + @include panel-footer; + padding: 12px 16px; +@@ -210,6 +216,11 @@ + background: $PR-blue-800; + } + ++ &:disabled { ++ opacity: 0.5; ++ cursor: not-allowed; ++ } ++ + .material-icons { + font-size: 18px; + } +diff --git a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.spec.ts b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.spec.ts +index 2f3176f6..9c94eac0 100644 +--- a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.spec.ts ++++ b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.spec.ts +@@ -578,6 +578,62 @@ describe('SidebarDatePickerComponent', () => { + }); + }); + ++ describe('validation', () => { ++ const invalidTimeOnlyInput = { ++ hours: '02', ++ minutes: '30', ++ seconds: '00', ++ format: 'pm', ++ } as const; ++ ++ it('should disable save and show an error when a time is entered without a complete date', () => { ++ component.open(); ++ component.onTimeChange({ ...invalidTimeOnlyInput }); ++ fixture.detectChanges(); ++ ++ expect(component.isEdtfValid()).toBeFalse(); ++ ++ const errorMessage = ++ fixture.nativeElement.querySelector('.pr-edtf-error'); ++ ++ expect(errorMessage).toBeTruthy(); ++ expect(errorMessage.textContent.trim()).not.toBe(''); ++ ++ const saveButton = fixture.nativeElement.querySelector('.pr-btn-save'); ++ ++ expect(saveButton.disabled).toBeTrue(); ++ }); ++ ++ it('should not emit saveClicked and should keep the dropdown open when the input is invalid', () => { ++ component.open(); ++ component.onTimeChange({ ...invalidTimeOnlyInput }); ++ fixture.detectChanges(); ++ ++ component.onSave(); ++ ++ expect(host.savedValue).toBeNull(); ++ expect(component.isDropdownOpen()).toBeTrue(); ++ }); ++ ++ it('should keep save enabled and show no error for a valid date', () => { ++ host.displayTime = { ++ date: { year: '1985', month: '05', day: '20' }, ++ time: { hours: '', minutes: '', seconds: '', format: 'am' }, ++ }; ++ fixture.detectChanges(); ++ ++ component.open(); ++ fixture.detectChanges(); ++ ++ expect(component.isEdtfValid()).toBeTrue(); ++ expect(fixture.nativeElement.querySelector('.pr-edtf-error')).toBeNull(); ++ ++ const saveButton = fixture.nativeElement.querySelector('.pr-btn-save'); ++ ++ expect(saveButton.disabled).toBeFalse(); ++ }); ++ }); ++ + describe('onCancel', () => { + it('should close dropdown and reset to input values', () => { + host.displayTime = { +diff --git a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.ts b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.ts +index 193b0c91..64c1666b 100644 +--- a/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.ts ++++ b/src/app/file-browser/components/sidebar-date-picker/sidebar-date-picker.component.ts +@@ -155,6 +155,23 @@ export class SidebarDatePickerComponent implements OnInit, OnChanges { + this._isOpenStart() ? this.endTimezone() : this.startTimezone(), + ); + ++ private edtfResult = computed<{ valid: boolean; errorMessage: string }>( ++ () => { ++ try { ++ this.edtfService.toEdtfDate(this.buildDateTimeModel()); ++ return { valid: true, errorMessage: '' }; ++ } catch (error) { ++ return { ++ valid: false, ++ errorMessage: error instanceof Error ? error.message : 'Invalid date', ++ }; ++ } ++ }, ++ ); ++ ++ isEdtfValid = computed(() => this.edtfResult().valid); ++ edtfErrorMessage = computed(() => this.edtfResult().errorMessage); ++ + rows = computed(() => { + const intervalLabel = this.intervalLabel(); + if (intervalLabel) { +@@ -266,15 +283,7 @@ export class SidebarDatePickerComponent implements OnInit, OnChanges { + + onMoreOptions(): void { + this.isDropdownOpen.set(false); +- +- const modalData: DateTimeModel = { +- qualifiers: { ...this._qualifiers() }, +- date: { ...this._date() }, +- time: { ...this._time() }, +- ...(this.buildEndSide() ?? {}), +- }; +- +- this.moreOptionsClicked.emit(modalData); ++ this.moreOptionsClicked.emit(this.buildDateTimeModel()); + } + + onCancel(): void { +@@ -283,15 +292,21 @@ export class SidebarDatePickerComponent implements OnInit, OnChanges { + } + + onSave(): void { +- const dateTimeModel: DateTimeModel = { ++ if (!this.isEdtfValid()) { ++ return; ++ } ++ ++ this.saveClicked.emit(this.buildDateTimeModel()); ++ this.isDropdownOpen.set(false); ++ } ++ ++ private buildDateTimeModel(): DateTimeModel { ++ return { + qualifiers: { ...this._qualifiers() }, + date: { ...this._date() }, + time: { ...this._time() }, + ...(this.buildEndSide() ?? {}), + }; +- +- this.saveClicked.emit(dateTimeModel); +- this.isDropdownOpen.set(false); + } + + private hasAnyQualifier(flags: DateQualifierFlags): boolean { +diff --git a/src/app/shared/components/message/message.component.scss b/src/app/shared/components/message/message.component.scss +index b2f047b2..4e75c8de 100644 +--- a/src/app/shared/components/message/message.component.scss ++++ b/src/app/shared/components/message/message.component.scss +@@ -8,7 +8,10 @@ $transition-length: 0.33s; + left: 0; + right: 0; + transform: translateY(-100%); +- z-index: 6; ++ // Must sit above full-screen components (z-index 10, e.g. the file viewer) ++ // and the date/time picker dropdowns (z-index 20) that open within them, ++ // otherwise the error banner renders but is hidden behind the overlay. ++ z-index: 30; + } + + .alert { +diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss +index 564686cf..93660b32 100644 +--- a/src/styles/_mixins.scss ++++ b/src/styles/_mixins.scss +@@ -55,6 +55,16 @@ + color: $red; + } + ++@mixin edtf-error-message { ++ font-size: 12px; ++ line-height: 16px; ++ color: $red; ++ word-wrap: break-word; ++ overflow-wrap: break-word; ++ white-space: normal; ++ min-width: 0; ++} ++ + @mixin icon-wrapper { + background: $PR-blue-25; + border-radius: 0 8px 8px 0; diff --git a/docs/financial-reports/financial-metrics-601066.json b/docs/financial-reports/financial-metrics-601066.json new file mode 100644 index 000000000..23d0b8c0e --- /dev/null +++ b/docs/financial-reports/financial-metrics-601066.json @@ -0,0 +1,107 @@ +{ + "symbol": "601066", + "name": "CSC Financial Co., Ltd. (中信建投证券)", + "unit": "CNY", + "metrics": [ + { + "report_date": "2026-03-31", + "total_assets": 779613662104.08, + "fixed_assets_net": 544338179.63, + "cash_and_equivalents": 220644705129.35, + "accounts_receivable": 13284898354.32, + "inventory": null, + "total_liabilities": 652739424810.28, + "trade_and_non_trade_payables": 26589634514.99, + "deferred_revenue": null, + "shareholders_equity": 126874237293.8, + "revenue": 7695568554.1, + "total_operating_costs": 3029257449.95, + "operating_profit": 4666311104.15, + "net_income_common_stock": 3667406478.67, + "net_cash_flow_from_operations": 31019165845.33, + "net_cash_flow_from_investing": -5857495259.23, + "net_cash_flow_from_financing": 29284240817.11, + "change_in_cash_and_equivalents": 54443762058.31 + }, + { + "report_date": "2025-12-31", + "total_assets": 676815790802.9, + "fixed_assets_net": 595080026.54, + "cash_and_equivalents": 176697854471.55, + "accounts_receivable": 9807003570.12, + "inventory": null, + "total_liabilities": 557665478763.26, + "trade_and_non_trade_payables": 22651439752.59, + "deferred_revenue": null, + "shareholders_equity": 119150312039.64, + "revenue": 23321714460.73, + "total_operating_costs": 11494949557.82, + "operating_profit": 11826764902.91, + "net_income_common_stock": 9439423821.46, + "net_cash_flow_from_operations": 83051673233.71, + "net_cash_flow_from_investing": -50173119748.29, + "net_cash_flow_from_financing": 15888872362.9, + "change_in_cash_and_equivalents": 48669011846.78 + }, + { + "report_date": "2025-09-30", + "total_assets": 662757008402.98, + "fixed_assets_net": 554042438.09, + "cash_and_equivalents": 166294606395.07, + "accounts_receivable": 9350053642.8, + "inventory": null, + "total_liabilities": 546930137407.78, + "trade_and_non_trade_payables": 19228469816.2, + "deferred_revenue": null, + "shareholders_equity": 115826870995.2, + "revenue": 17289286297.97, + "total_operating_costs": 8764126310.7, + "operating_profit": 8525159987.27, + "net_income_common_stock": 7088639471.04, + "net_cash_flow_from_operations": 57951521124.99, + "net_cash_flow_from_investing": -40963886551.58, + "net_cash_flow_from_financing": 16687786246.02, + "change_in_cash_and_equivalents": 33492832156.85 + }, + { + "report_date": "2025-06-30", + "total_assets": 612364018741.91, + "fixed_assets_net": 597369454.19, + "cash_and_equivalents": 170471829845.13, + "accounts_receivable": 12135297964.97, + "inventory": null, + "total_liabilities": 502524274453.19, + "trade_and_non_trade_payables": 18378440896.2, + "deferred_revenue": null, + "shareholders_equity": 109839744288.72, + "revenue": 10739893576.57, + "total_operating_costs": 5404721374.71, + "operating_profit": 5335172201.86, + "net_income_common_stock": 4508536107.4, + "net_cash_flow_from_operations": 60125763563.1, + "net_cash_flow_from_investing": -33958880895.28, + "net_cash_flow_from_financing": -7776991191.87, + "change_in_cash_and_equivalents": 18276713297.29 + }, + { + "report_date": "2025-03-31", + "total_assets": 600506434259.14, + "fixed_assets_net": 642339881.06, + "cash_and_equivalents": 139534421912.97, + "accounts_receivable": 8899557059.15, + "inventory": null, + "total_liabilities": 495220943736.45, + "trade_and_non_trade_payables": 19859870287.19, + "deferred_revenue": null, + "shareholders_equity": 105285490522.69, + "revenue": 4742593553.05, + "total_operating_costs": 2639564490.32, + "operating_profit": 2103029062.73, + "net_income_common_stock": 1842645806.07, + "net_cash_flow_from_operations": 20929390912.82, + "net_cash_flow_from_investing": -21955835088.2, + "net_cash_flow_from_financing": 997830067.18, + "change_in_cash_and_equivalents": 7096877.39 + } + ] +} \ No newline at end of file diff --git a/docs/financial-reports/financial-metrics-601066.md b/docs/financial-reports/financial-metrics-601066.md new file mode 100644 index 000000000..fa50c0712 --- /dev/null +++ b/docs/financial-reports/financial-metrics-601066.md @@ -0,0 +1,29 @@ +# Financial Metrics Report — Account 601066 (CSC Financial / 中信建投证券) + +Source: akshare (eastmoney) key metrics from the three major financial statements. All monetary values in 亿元 (100M CNY) unless noted. + +| Report Date | Total Assets | Total Liabilities | Shareholders' Equity | Revenue | Operating Profit | Net Income (Common) | +|---|---|---|---|---|---|---| +| 2026-03-31 | 7,796.14 | 6,527.39 | 1,268.74 | 76.96 | 46.66 | 36.67 | +| 2025-12-31 | 6,768.16 | 5,576.65 | 1,191.50 | 233.22 | 118.27 | 94.39 | +| 2025-09-30 | 6,627.57 | 5,469.30 | 1,158.27 | 172.89 | 85.25 | 70.89 | +| 2025-06-30 | 6,123.64 | 5,025.24 | 1,098.40 | 107.40 | 53.35 | 45.09 | +| 2025-03-31 | 6,005.06 | 4,952.21 | 1,052.85 | 47.43 | 21.03 | 18.43 | + +| Report Date | Cash & Equivalents | Accounts Receivable | Payables | CFO | CFI | CFF | Δ Cash | +|---|---|---|---|---|---|---|---| +| 2026-03-31 | 2,206.45 | 132.85 | 265.90 | 310.19 | -58.57 | 292.84 | 544.44 | +| 2025-12-31 | 1,766.98 | 98.07 | 226.51 | 830.52 | -501.73 | 158.89 | 486.69 | +| 2025-09-30 | 1,662.95 | 93.50 | 192.28 | 579.52 | -409.64 | 166.88 | 334.93 | +| 2025-06-30 | 1,704.72 | 121.35 | 183.78 | 601.26 | -339.59 | -77.77 | 182.77 | +| 2025-03-31 | 1,395.34 | 89.00 | 198.60 | 209.29 | -219.56 | 9.98 | 0.07 | + +## Highlights (latest quarter 2026-03-31) + +- Total assets: 7,796.14 亿元 (+15.2% vs 2025-12-31) +- Shareholders' equity: 1,268.74 亿元 (+6.5%) +- Quarterly revenue: 76.96 亿元; operating profit: 46.66 亿元; net income: 36.67 亿元 +- Net operating cash flow: 310.19 亿元 +- Debt-to-asset ratio: 83.7% + +Raw JSON: `financial-metrics-601066.json` in this directory.
Date +- +-
Date ++ ++
Uploaded{{ currentRecord.createdDT | date }}