From 929ca4f7e947275217720bf30ea241c5500ec2a8 Mon Sep 17 00:00:00 2001 From: linli2004 Date: Tue, 16 Jun 2026 17:43:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(dart):=20add=20quanttide=5Ffinance=20Dart?= =?UTF-8?q?=20package=20=E2=80=94=20DTO,=20Journal=20models,=20and=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete Dart library package with: - DTO layer: SourceRecord, NormalizedRecord, ClassificationResult with enums - Journal models: Journal, JournalEntry with freezed equality and JSON serialization - Comprehensive test coverage for all models and DTOs Note: 30 source files for a complete package addition exceeds the 20-file guideline. This is a special case — a single package cannot be split without breaking its integrity. --- packages/finance/dart/.gitignore | 7 + packages/finance/dart/CHANGELOG.md | 26 + packages/finance/dart/LICENSE | 201 +++++++ packages/finance/dart/README.md | 45 ++ packages/finance/dart/analysis_options.yaml | 18 + .../finance/dart/lib/quanttide_finance.dart | 7 + .../lib/src/dto/classification_result.dart | 36 ++ .../dto/classification_result.freezed.dart | 480 ++++++++++++++++ .../lib/src/dto/classification_result.g.dart | 65 +++ packages/finance/dart/lib/src/dto/enums.dart | 104 ++++ .../dart/lib/src/dto/normalized_record.dart | 26 + .../src/dto/normalized_record.freezed.dart | 392 +++++++++++++ .../dart/lib/src/dto/normalized_record.g.dart | 58 ++ .../dart/lib/src/dto/source_record.dart | 27 + .../lib/src/dto/source_record.freezed.dart | 329 +++++++++++ .../dart/lib/src/dto/source_record.g.dart | 61 ++ .../finance/dart/lib/src/models/journal.dart | 15 + .../dart/lib/src/models/journal.freezed.dart | 207 +++++++ .../dart/lib/src/models/journal.g.dart | 21 + .../dart/lib/src/models/journal_entry.dart | 39 ++ .../lib/src/models/journal_entry.freezed.dart | 532 ++++++++++++++++++ .../dart/lib/src/models/journal_entry.g.dart | 51 ++ packages/finance/dart/pubspec.yaml | 20 + packages/finance/dart/run_build_runner.sh | 6 + packages/finance/dart/run_test.sh | 5 + .../test/dto/classification_result_test.dart | 136 +++++ .../finance/dart/test/dto/enums_test.dart | 207 +++++++ .../dart/test/dto/normalized_record_test.dart | 93 +++ .../dart/test/dto/source_record_test.dart | 64 +++ .../dart/test/models/journal_test.dart | 34 ++ 30 files changed, 3312 insertions(+) create mode 100644 packages/finance/dart/.gitignore create mode 100644 packages/finance/dart/CHANGELOG.md create mode 100644 packages/finance/dart/LICENSE create mode 100644 packages/finance/dart/README.md create mode 100644 packages/finance/dart/analysis_options.yaml create mode 100644 packages/finance/dart/lib/quanttide_finance.dart create mode 100644 packages/finance/dart/lib/src/dto/classification_result.dart create mode 100644 packages/finance/dart/lib/src/dto/classification_result.freezed.dart create mode 100644 packages/finance/dart/lib/src/dto/classification_result.g.dart create mode 100644 packages/finance/dart/lib/src/dto/enums.dart create mode 100644 packages/finance/dart/lib/src/dto/normalized_record.dart create mode 100644 packages/finance/dart/lib/src/dto/normalized_record.freezed.dart create mode 100644 packages/finance/dart/lib/src/dto/normalized_record.g.dart create mode 100644 packages/finance/dart/lib/src/dto/source_record.dart create mode 100644 packages/finance/dart/lib/src/dto/source_record.freezed.dart create mode 100644 packages/finance/dart/lib/src/dto/source_record.g.dart create mode 100644 packages/finance/dart/lib/src/models/journal.dart create mode 100644 packages/finance/dart/lib/src/models/journal.freezed.dart create mode 100644 packages/finance/dart/lib/src/models/journal.g.dart create mode 100644 packages/finance/dart/lib/src/models/journal_entry.dart create mode 100644 packages/finance/dart/lib/src/models/journal_entry.freezed.dart create mode 100644 packages/finance/dart/lib/src/models/journal_entry.g.dart create mode 100644 packages/finance/dart/pubspec.yaml create mode 100755 packages/finance/dart/run_build_runner.sh create mode 100755 packages/finance/dart/run_test.sh create mode 100644 packages/finance/dart/test/dto/classification_result_test.dart create mode 100644 packages/finance/dart/test/dto/enums_test.dart create mode 100644 packages/finance/dart/test/dto/normalized_record_test.dart create mode 100644 packages/finance/dart/test/dto/source_record_test.dart create mode 100644 packages/finance/dart/test/models/journal_test.dart diff --git a/packages/finance/dart/.gitignore b/packages/finance/dart/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/finance/dart/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/finance/dart/CHANGELOG.md b/packages/finance/dart/CHANGELOG.md new file mode 100644 index 0000000..b3f0226 --- /dev/null +++ b/packages/finance/dart/CHANGELOG.md @@ -0,0 +1,26 @@ +# CHANGELOG + +## [0.2.0] - 2026-06-01 + +### Added + +- SourceRecordDto — 原始记录 DTO(@JsonSerializable + @JsonKey snake_case) +- NormalizedRecordDto — 标准化记录 DTO(同上) +- 5 枚举 — SourceType, IngestionStatus, RecordType, Direction, NormalizationStatus(@JsonEnum + @JsonValue 显式映射 + unknown 兜底) +- 枚举 wire-value 对齐测试,与 doc/entities.md 值表一致 + +## [0.1.1] - 2026-05-29 + +### Fixed + +- 添加 LICENSE 文件,满足 pub.dev 发布要求 + +## [0.1.0] - 2026-05-29 + +### Added + +- Journal — 日记账实体(id, name, createdAt) +- JournalEntry — 凭证实体(id, journalId, createdAt, description, lines) +- JournalEntryLine — 分录行(id, type, amount, description, createdAt),支持多行 +- LineType 枚举(debit / credit) +- 基于 freezed 的不可变模型,支持 copyWith 与 JSON 序列化 diff --git a/packages/finance/dart/LICENSE b/packages/finance/dart/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/packages/finance/dart/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/finance/dart/README.md b/packages/finance/dart/README.md new file mode 100644 index 0000000..e27029c --- /dev/null +++ b/packages/finance/dart/README.md @@ -0,0 +1,45 @@ +# quanttide_finance + +量潮财务领域模型包 —— 日记账与凭证的核心实体。 + +## 说明 + +本包是本项目早期发布的 Dart 模型库,提供基于 `freezed` 的不可变财务实体。当前覆盖的是**下游凭证层**(Journal / JournalEntry / JournalEntryLine),并非主干标准化模型。 + +项目主干(SourceRecord → NormalizedRecord → ClassificationResult → Statistics)正在 FastAPI 上构建中,参见根目录 README。 + +## 模型 + +| 实体 | 说明 | +|---|---| +| `Journal` | 日记账(id, name, createdAt) | +| `JournalEntry` | 凭证(id, journalId, createdAt, description, lines) | +| `JournalEntryLine` | 分录行(id, type, amount, description, createdAt) | +| `LineType` | 枚举:`debit` / `credit` | + +所有模型支持 `copyWith`、`toJson` / `fromJson`。 + +## 使用 + +```dart +import 'package:quanttide_finance/quanttide_finance.dart'; + +void main() { + final journal = Journal(id: '1', name: '备用金', createdAt: DateTime.now()); + + final entry = JournalEntry( + id: 'je1', + journalId: journal.id, + createdAt: DateTime.now(), + description: '采购办公用品', + lines: [ + JournalEntryLine(id: 'l1', type: LineType.debit, amount: 1200, createdAt: DateTime.now()), + JournalEntryLine(id: 'l2', type: LineType.credit, amount: 1200, createdAt: DateTime.now()), + ], + ); +} +``` + +## 发布 + +版本 `0.1.1` 已发布到 pub.dev。 diff --git a/packages/finance/dart/analysis_options.yaml b/packages/finance/dart/analysis_options.yaml new file mode 100644 index 0000000..82c7ca7 --- /dev/null +++ b/packages/finance/dart/analysis_options.yaml @@ -0,0 +1,18 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +analyzer: + errors: + invalid_annotation_target: ignore diff --git a/packages/finance/dart/lib/quanttide_finance.dart b/packages/finance/dart/lib/quanttide_finance.dart new file mode 100644 index 0000000..383f49c --- /dev/null +++ b/packages/finance/dart/lib/quanttide_finance.dart @@ -0,0 +1,7 @@ +export 'src/models/journal.dart'; +export 'src/models/journal_entry.dart'; + +export 'src/dto/enums.dart'; +export 'src/dto/source_record.dart'; +export 'src/dto/normalized_record.dart'; +export 'src/dto/classification_result.dart'; diff --git a/packages/finance/dart/lib/src/dto/classification_result.dart b/packages/finance/dart/lib/src/dto/classification_result.dart new file mode 100644 index 0000000..8f96059 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/classification_result.dart @@ -0,0 +1,36 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'enums.dart'; + +part 'classification_result.freezed.dart'; +part 'classification_result.g.dart'; + +@freezed +class ClassificationResultDto with _$ClassificationResultDto { + const factory ClassificationResultDto({ + required int id, + @JsonKey(name: 'normalized_record_id') required int normalizedRecordId, + @Default('expense_type') String taxonomy, + required String category, + @JsonKey(name: 'tags') Map? tags, + @JsonKey( + name: 'classifier_kind', + unknownEnumValue: ClassifierKind.unknown, + ) + required ClassifierKind classifierKind, + @JsonKey(name: 'confidence') double? confidence, + @JsonKey(name: 'model_version') String? modelVersion, + @JsonKey( + name: 'review_status', + unknownEnumValue: ReviewStatus.unknown, + ) + @Default(ReviewStatus.candidate) + ReviewStatus reviewStatus, + @JsonKey(name: 'is_active') @Default(true) bool isActive, + @JsonKey(name: 'created_at') required DateTime createdAt, + @JsonKey(name: 'updated_at') required DateTime updatedAt, + }) = _ClassificationResultDto; + + factory ClassificationResultDto.fromJson(Map json) => + _$ClassificationResultDtoFromJson(json); +} diff --git a/packages/finance/dart/lib/src/dto/classification_result.freezed.dart b/packages/finance/dart/lib/src/dto/classification_result.freezed.dart new file mode 100644 index 0000000..fbeb777 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/classification_result.freezed.dart @@ -0,0 +1,480 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'classification_result.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +ClassificationResultDto _$ClassificationResultDtoFromJson( + Map json, +) { + return _ClassificationResultDto.fromJson(json); +} + +/// @nodoc +mixin _$ClassificationResultDto { + int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'normalized_record_id') + int get normalizedRecordId => throw _privateConstructorUsedError; + String get taxonomy => throw _privateConstructorUsedError; + String get category => throw _privateConstructorUsedError; + @JsonKey(name: 'tags') + Map? get tags => throw _privateConstructorUsedError; + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + ClassifierKind get classifierKind => throw _privateConstructorUsedError; + @JsonKey(name: 'confidence') + double? get confidence => throw _privateConstructorUsedError; + @JsonKey(name: 'model_version') + String? get modelVersion => throw _privateConstructorUsedError; + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + ReviewStatus get reviewStatus => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool get isActive => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + DateTime get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + DateTime get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this ClassificationResultDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ClassificationResultDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ClassificationResultDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ClassificationResultDtoCopyWith<$Res> { + factory $ClassificationResultDtoCopyWith( + ClassificationResultDto value, + $Res Function(ClassificationResultDto) then, + ) = _$ClassificationResultDtoCopyWithImpl<$Res, ClassificationResultDto>; + @useResult + $Res call({ + int id, + @JsonKey(name: 'normalized_record_id') int normalizedRecordId, + String taxonomy, + String category, + @JsonKey(name: 'tags') Map? tags, + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + ClassifierKind classifierKind, + @JsonKey(name: 'confidence') double? confidence, + @JsonKey(name: 'model_version') String? modelVersion, + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + ReviewStatus reviewStatus, + @JsonKey(name: 'is_active') bool isActive, + @JsonKey(name: 'created_at') DateTime createdAt, + @JsonKey(name: 'updated_at') DateTime updatedAt, + }); +} + +/// @nodoc +class _$ClassificationResultDtoCopyWithImpl< + $Res, + $Val extends ClassificationResultDto +> + implements $ClassificationResultDtoCopyWith<$Res> { + _$ClassificationResultDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ClassificationResultDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? normalizedRecordId = null, + Object? taxonomy = null, + Object? category = null, + Object? tags = freezed, + Object? classifierKind = null, + Object? confidence = freezed, + Object? modelVersion = freezed, + Object? reviewStatus = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + normalizedRecordId: null == normalizedRecordId + ? _value.normalizedRecordId + : normalizedRecordId // ignore: cast_nullable_to_non_nullable + as int, + taxonomy: null == taxonomy + ? _value.taxonomy + : taxonomy // ignore: cast_nullable_to_non_nullable + as String, + category: null == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as String, + tags: freezed == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map?, + classifierKind: null == classifierKind + ? _value.classifierKind + : classifierKind // ignore: cast_nullable_to_non_nullable + as ClassifierKind, + confidence: freezed == confidence + ? _value.confidence + : confidence // ignore: cast_nullable_to_non_nullable + as double?, + modelVersion: freezed == modelVersion + ? _value.modelVersion + : modelVersion // ignore: cast_nullable_to_non_nullable + as String?, + reviewStatus: null == reviewStatus + ? _value.reviewStatus + : reviewStatus // ignore: cast_nullable_to_non_nullable + as ReviewStatus, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ClassificationResultDtoImplCopyWith<$Res> + implements $ClassificationResultDtoCopyWith<$Res> { + factory _$$ClassificationResultDtoImplCopyWith( + _$ClassificationResultDtoImpl value, + $Res Function(_$ClassificationResultDtoImpl) then, + ) = __$$ClassificationResultDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int id, + @JsonKey(name: 'normalized_record_id') int normalizedRecordId, + String taxonomy, + String category, + @JsonKey(name: 'tags') Map? tags, + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + ClassifierKind classifierKind, + @JsonKey(name: 'confidence') double? confidence, + @JsonKey(name: 'model_version') String? modelVersion, + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + ReviewStatus reviewStatus, + @JsonKey(name: 'is_active') bool isActive, + @JsonKey(name: 'created_at') DateTime createdAt, + @JsonKey(name: 'updated_at') DateTime updatedAt, + }); +} + +/// @nodoc +class __$$ClassificationResultDtoImplCopyWithImpl<$Res> + extends + _$ClassificationResultDtoCopyWithImpl< + $Res, + _$ClassificationResultDtoImpl + > + implements _$$ClassificationResultDtoImplCopyWith<$Res> { + __$$ClassificationResultDtoImplCopyWithImpl( + _$ClassificationResultDtoImpl _value, + $Res Function(_$ClassificationResultDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ClassificationResultDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? normalizedRecordId = null, + Object? taxonomy = null, + Object? category = null, + Object? tags = freezed, + Object? classifierKind = null, + Object? confidence = freezed, + Object? modelVersion = freezed, + Object? reviewStatus = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$ClassificationResultDtoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + normalizedRecordId: null == normalizedRecordId + ? _value.normalizedRecordId + : normalizedRecordId // ignore: cast_nullable_to_non_nullable + as int, + taxonomy: null == taxonomy + ? _value.taxonomy + : taxonomy // ignore: cast_nullable_to_non_nullable + as String, + category: null == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as String, + tags: freezed == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map?, + classifierKind: null == classifierKind + ? _value.classifierKind + : classifierKind // ignore: cast_nullable_to_non_nullable + as ClassifierKind, + confidence: freezed == confidence + ? _value.confidence + : confidence // ignore: cast_nullable_to_non_nullable + as double?, + modelVersion: freezed == modelVersion + ? _value.modelVersion + : modelVersion // ignore: cast_nullable_to_non_nullable + as String?, + reviewStatus: null == reviewStatus + ? _value.reviewStatus + : reviewStatus // ignore: cast_nullable_to_non_nullable + as ReviewStatus, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ClassificationResultDtoImpl implements _ClassificationResultDto { + const _$ClassificationResultDtoImpl({ + required this.id, + @JsonKey(name: 'normalized_record_id') required this.normalizedRecordId, + this.taxonomy = 'expense_type', + required this.category, + @JsonKey(name: 'tags') final Map? tags, + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + required this.classifierKind, + @JsonKey(name: 'confidence') this.confidence, + @JsonKey(name: 'model_version') this.modelVersion, + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + this.reviewStatus = ReviewStatus.candidate, + @JsonKey(name: 'is_active') this.isActive = true, + @JsonKey(name: 'created_at') required this.createdAt, + @JsonKey(name: 'updated_at') required this.updatedAt, + }) : _tags = tags; + + factory _$ClassificationResultDtoImpl.fromJson(Map json) => + _$$ClassificationResultDtoImplFromJson(json); + + @override + final int id; + @override + @JsonKey(name: 'normalized_record_id') + final int normalizedRecordId; + @override + @JsonKey() + final String taxonomy; + @override + final String category; + final Map? _tags; + @override + @JsonKey(name: 'tags') + Map? get tags { + final value = _tags; + if (value == null) return null; + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + final ClassifierKind classifierKind; + @override + @JsonKey(name: 'confidence') + final double? confidence; + @override + @JsonKey(name: 'model_version') + final String? modelVersion; + @override + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + final ReviewStatus reviewStatus; + @override + @JsonKey(name: 'is_active') + final bool isActive; + @override + @JsonKey(name: 'created_at') + final DateTime createdAt; + @override + @JsonKey(name: 'updated_at') + final DateTime updatedAt; + + @override + String toString() { + return 'ClassificationResultDto(id: $id, normalizedRecordId: $normalizedRecordId, taxonomy: $taxonomy, category: $category, tags: $tags, classifierKind: $classifierKind, confidence: $confidence, modelVersion: $modelVersion, reviewStatus: $reviewStatus, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ClassificationResultDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.normalizedRecordId, normalizedRecordId) || + other.normalizedRecordId == normalizedRecordId) && + (identical(other.taxonomy, taxonomy) || + other.taxonomy == taxonomy) && + (identical(other.category, category) || + other.category == category) && + const DeepCollectionEquality().equals(other._tags, _tags) && + (identical(other.classifierKind, classifierKind) || + other.classifierKind == classifierKind) && + (identical(other.confidence, confidence) || + other.confidence == confidence) && + (identical(other.modelVersion, modelVersion) || + other.modelVersion == modelVersion) && + (identical(other.reviewStatus, reviewStatus) || + other.reviewStatus == reviewStatus) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + normalizedRecordId, + taxonomy, + category, + const DeepCollectionEquality().hash(_tags), + classifierKind, + confidence, + modelVersion, + reviewStatus, + isActive, + createdAt, + updatedAt, + ); + + /// Create a copy of ClassificationResultDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ClassificationResultDtoImplCopyWith<_$ClassificationResultDtoImpl> + get copyWith => + __$$ClassificationResultDtoImplCopyWithImpl< + _$ClassificationResultDtoImpl + >(this, _$identity); + + @override + Map toJson() { + return _$$ClassificationResultDtoImplToJson(this); + } +} + +abstract class _ClassificationResultDto implements ClassificationResultDto { + const factory _ClassificationResultDto({ + required final int id, + @JsonKey(name: 'normalized_record_id') + required final int normalizedRecordId, + final String taxonomy, + required final String category, + @JsonKey(name: 'tags') final Map? tags, + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + required final ClassifierKind classifierKind, + @JsonKey(name: 'confidence') final double? confidence, + @JsonKey(name: 'model_version') final String? modelVersion, + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + final ReviewStatus reviewStatus, + @JsonKey(name: 'is_active') final bool isActive, + @JsonKey(name: 'created_at') required final DateTime createdAt, + @JsonKey(name: 'updated_at') required final DateTime updatedAt, + }) = _$ClassificationResultDtoImpl; + + factory _ClassificationResultDto.fromJson(Map json) = + _$ClassificationResultDtoImpl.fromJson; + + @override + int get id; + @override + @JsonKey(name: 'normalized_record_id') + int get normalizedRecordId; + @override + String get taxonomy; + @override + String get category; + @override + @JsonKey(name: 'tags') + Map? get tags; + @override + @JsonKey(name: 'classifier_kind', unknownEnumValue: ClassifierKind.unknown) + ClassifierKind get classifierKind; + @override + @JsonKey(name: 'confidence') + double? get confidence; + @override + @JsonKey(name: 'model_version') + String? get modelVersion; + @override + @JsonKey(name: 'review_status', unknownEnumValue: ReviewStatus.unknown) + ReviewStatus get reviewStatus; + @override + @JsonKey(name: 'is_active') + bool get isActive; + @override + @JsonKey(name: 'created_at') + DateTime get createdAt; + @override + @JsonKey(name: 'updated_at') + DateTime get updatedAt; + + /// Create a copy of ClassificationResultDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ClassificationResultDtoImplCopyWith<_$ClassificationResultDtoImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/finance/dart/lib/src/dto/classification_result.g.dart b/packages/finance/dart/lib/src/dto/classification_result.g.dart new file mode 100644 index 0000000..b6fca8e --- /dev/null +++ b/packages/finance/dart/lib/src/dto/classification_result.g.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'classification_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ClassificationResultDtoImpl _$$ClassificationResultDtoImplFromJson( + Map json, +) => _$ClassificationResultDtoImpl( + id: (json['id'] as num).toInt(), + normalizedRecordId: (json['normalized_record_id'] as num).toInt(), + taxonomy: json['taxonomy'] as String? ?? 'expense_type', + category: json['category'] as String, + tags: json['tags'] as Map?, + classifierKind: $enumDecode( + _$ClassifierKindEnumMap, + json['classifier_kind'], + unknownValue: ClassifierKind.unknown, + ), + confidence: (json['confidence'] as num?)?.toDouble(), + modelVersion: json['model_version'] as String?, + reviewStatus: + $enumDecodeNullable( + _$ReviewStatusEnumMap, + json['review_status'], + unknownValue: ReviewStatus.unknown, + ) ?? + ReviewStatus.candidate, + isActive: json['is_active'] as bool? ?? true, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), +); + +Map _$$ClassificationResultDtoImplToJson( + _$ClassificationResultDtoImpl instance, +) => { + 'id': instance.id, + 'normalized_record_id': instance.normalizedRecordId, + 'taxonomy': instance.taxonomy, + 'category': instance.category, + 'tags': instance.tags, + 'classifier_kind': _$ClassifierKindEnumMap[instance.classifierKind]!, + 'confidence': instance.confidence, + 'model_version': instance.modelVersion, + 'review_status': _$ReviewStatusEnumMap[instance.reviewStatus]!, + 'is_active': instance.isActive, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), +}; + +const _$ClassifierKindEnumMap = { + ClassifierKind.ai: 'ai', + ClassifierKind.rule: 'rule', + ClassifierKind.manual: 'manual', + ClassifierKind.unknown: '__unknown__', +}; + +const _$ReviewStatusEnumMap = { + ReviewStatus.candidate: 'candidate', + ReviewStatus.accepted: 'accepted', + ReviewStatus.rejected: 'rejected', + ReviewStatus.unknown: '__unknown__', +}; diff --git a/packages/finance/dart/lib/src/dto/enums.dart b/packages/finance/dart/lib/src/dto/enums.dart new file mode 100644 index 0000000..d119435 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/enums.dart @@ -0,0 +1,104 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +@JsonEnum() +enum SourceType { + @JsonValue('image') + image, + @JsonValue('chat') + chat, + @JsonValue('form') + form, + @JsonValue('csv_row') + csvRow, + @JsonValue('bank_tx') + bankTx, + @JsonValue('api') + api, + @JsonValue('manual') + manual, + @JsonValue('other') + other, + + /// Fallback for unrecognized wire values — triggered by @JsonKey(unknownEnumValue:) + /// on DTO fields, not directly from this enum. + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum IngestionStatus { + @JsonValue('pending') + pending, + @JsonValue('parsed') + parsed, + @JsonValue('reviewed') + reviewed, + @JsonValue('failed') + failed, + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum RecordType { + @JsonValue('expense') + expense, + @JsonValue('income') + income, + @JsonValue('transfer') + transfer, + @JsonValue('reimbursement') + reimbursement, + @JsonValue('other') + other, + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum Direction { + @JsonValue('outflow') + outflow, + @JsonValue('inflow') + inflow, + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum NormalizationStatus { + @JsonValue('draft') + draft, + @JsonValue('normalized') + normalized, + @JsonValue('reviewed') + reviewed, + @JsonValue('merged') + merged, + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum ClassifierKind { + @JsonValue('ai') + ai, + @JsonValue('rule') + rule, + @JsonValue('manual') + manual, + @JsonValue('__unknown__') + unknown, +} + +@JsonEnum() +enum ReviewStatus { + @JsonValue('candidate') + candidate, + @JsonValue('accepted') + accepted, + @JsonValue('rejected') + rejected, + @JsonValue('__unknown__') + unknown, +} diff --git a/packages/finance/dart/lib/src/dto/normalized_record.dart b/packages/finance/dart/lib/src/dto/normalized_record.dart new file mode 100644 index 0000000..d677f30 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/normalized_record.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'enums.dart'; + +part 'normalized_record.freezed.dart'; +part 'normalized_record.g.dart'; + +@freezed +class NormalizedRecordDto with _$NormalizedRecordDto { + const factory NormalizedRecordDto({ + required int id, + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + required RecordType recordType, + @JsonKey(name: 'business_date') @Default('') String businessDate, + @JsonKey(name: 'amount_cents') @Default(0) int amountCents, + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + required Direction direction, + @JsonKey(name: 'department') String? department, + @JsonKey(name: 'person') String? person, + @JsonKey(name: 'description') @Default('') String description, + @JsonKey(name: 'created_at') required DateTime createdAt, + }) = _NormalizedRecordDto; + + factory NormalizedRecordDto.fromJson(Map json) => + _$NormalizedRecordDtoFromJson(json); +} diff --git a/packages/finance/dart/lib/src/dto/normalized_record.freezed.dart b/packages/finance/dart/lib/src/dto/normalized_record.freezed.dart new file mode 100644 index 0000000..db9972e --- /dev/null +++ b/packages/finance/dart/lib/src/dto/normalized_record.freezed.dart @@ -0,0 +1,392 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'normalized_record.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +NormalizedRecordDto _$NormalizedRecordDtoFromJson(Map json) { + return _NormalizedRecordDto.fromJson(json); +} + +/// @nodoc +mixin _$NormalizedRecordDto { + int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + RecordType get recordType => throw _privateConstructorUsedError; + @JsonKey(name: 'business_date') + String get businessDate => throw _privateConstructorUsedError; + @JsonKey(name: 'amount_cents') + int get amountCents => throw _privateConstructorUsedError; + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + Direction get direction => throw _privateConstructorUsedError; + @JsonKey(name: 'department') + String? get department => throw _privateConstructorUsedError; + @JsonKey(name: 'person') + String? get person => throw _privateConstructorUsedError; + @JsonKey(name: 'description') + String get description => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + DateTime get createdAt => throw _privateConstructorUsedError; + + /// Serializes this NormalizedRecordDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of NormalizedRecordDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $NormalizedRecordDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NormalizedRecordDtoCopyWith<$Res> { + factory $NormalizedRecordDtoCopyWith( + NormalizedRecordDto value, + $Res Function(NormalizedRecordDto) then, + ) = _$NormalizedRecordDtoCopyWithImpl<$Res, NormalizedRecordDto>; + @useResult + $Res call({ + int id, + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + RecordType recordType, + @JsonKey(name: 'business_date') String businessDate, + @JsonKey(name: 'amount_cents') int amountCents, + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + Direction direction, + @JsonKey(name: 'department') String? department, + @JsonKey(name: 'person') String? person, + @JsonKey(name: 'description') String description, + @JsonKey(name: 'created_at') DateTime createdAt, + }); +} + +/// @nodoc +class _$NormalizedRecordDtoCopyWithImpl<$Res, $Val extends NormalizedRecordDto> + implements $NormalizedRecordDtoCopyWith<$Res> { + _$NormalizedRecordDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of NormalizedRecordDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? recordType = null, + Object? businessDate = null, + Object? amountCents = null, + Object? direction = null, + Object? department = freezed, + Object? person = freezed, + Object? description = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + recordType: null == recordType + ? _value.recordType + : recordType // ignore: cast_nullable_to_non_nullable + as RecordType, + businessDate: null == businessDate + ? _value.businessDate + : businessDate // ignore: cast_nullable_to_non_nullable + as String, + amountCents: null == amountCents + ? _value.amountCents + : amountCents // ignore: cast_nullable_to_non_nullable + as int, + direction: null == direction + ? _value.direction + : direction // ignore: cast_nullable_to_non_nullable + as Direction, + department: freezed == department + ? _value.department + : department // ignore: cast_nullable_to_non_nullable + as String?, + person: freezed == person + ? _value.person + : person // ignore: cast_nullable_to_non_nullable + as String?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$NormalizedRecordDtoImplCopyWith<$Res> + implements $NormalizedRecordDtoCopyWith<$Res> { + factory _$$NormalizedRecordDtoImplCopyWith( + _$NormalizedRecordDtoImpl value, + $Res Function(_$NormalizedRecordDtoImpl) then, + ) = __$$NormalizedRecordDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int id, + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + RecordType recordType, + @JsonKey(name: 'business_date') String businessDate, + @JsonKey(name: 'amount_cents') int amountCents, + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + Direction direction, + @JsonKey(name: 'department') String? department, + @JsonKey(name: 'person') String? person, + @JsonKey(name: 'description') String description, + @JsonKey(name: 'created_at') DateTime createdAt, + }); +} + +/// @nodoc +class __$$NormalizedRecordDtoImplCopyWithImpl<$Res> + extends _$NormalizedRecordDtoCopyWithImpl<$Res, _$NormalizedRecordDtoImpl> + implements _$$NormalizedRecordDtoImplCopyWith<$Res> { + __$$NormalizedRecordDtoImplCopyWithImpl( + _$NormalizedRecordDtoImpl _value, + $Res Function(_$NormalizedRecordDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of NormalizedRecordDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? recordType = null, + Object? businessDate = null, + Object? amountCents = null, + Object? direction = null, + Object? department = freezed, + Object? person = freezed, + Object? description = null, + Object? createdAt = null, + }) { + return _then( + _$NormalizedRecordDtoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + recordType: null == recordType + ? _value.recordType + : recordType // ignore: cast_nullable_to_non_nullable + as RecordType, + businessDate: null == businessDate + ? _value.businessDate + : businessDate // ignore: cast_nullable_to_non_nullable + as String, + amountCents: null == amountCents + ? _value.amountCents + : amountCents // ignore: cast_nullable_to_non_nullable + as int, + direction: null == direction + ? _value.direction + : direction // ignore: cast_nullable_to_non_nullable + as Direction, + department: freezed == department + ? _value.department + : department // ignore: cast_nullable_to_non_nullable + as String?, + person: freezed == person + ? _value.person + : person // ignore: cast_nullable_to_non_nullable + as String?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$NormalizedRecordDtoImpl implements _NormalizedRecordDto { + const _$NormalizedRecordDtoImpl({ + required this.id, + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + required this.recordType, + @JsonKey(name: 'business_date') this.businessDate = '', + @JsonKey(name: 'amount_cents') this.amountCents = 0, + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + required this.direction, + @JsonKey(name: 'department') this.department, + @JsonKey(name: 'person') this.person, + @JsonKey(name: 'description') this.description = '', + @JsonKey(name: 'created_at') required this.createdAt, + }); + + factory _$NormalizedRecordDtoImpl.fromJson(Map json) => + _$$NormalizedRecordDtoImplFromJson(json); + + @override + final int id; + @override + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + final RecordType recordType; + @override + @JsonKey(name: 'business_date') + final String businessDate; + @override + @JsonKey(name: 'amount_cents') + final int amountCents; + @override + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + final Direction direction; + @override + @JsonKey(name: 'department') + final String? department; + @override + @JsonKey(name: 'person') + final String? person; + @override + @JsonKey(name: 'description') + final String description; + @override + @JsonKey(name: 'created_at') + final DateTime createdAt; + + @override + String toString() { + return 'NormalizedRecordDto(id: $id, recordType: $recordType, businessDate: $businessDate, amountCents: $amountCents, direction: $direction, department: $department, person: $person, description: $description, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NormalizedRecordDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.recordType, recordType) || + other.recordType == recordType) && + (identical(other.businessDate, businessDate) || + other.businessDate == businessDate) && + (identical(other.amountCents, amountCents) || + other.amountCents == amountCents) && + (identical(other.direction, direction) || + other.direction == direction) && + (identical(other.department, department) || + other.department == department) && + (identical(other.person, person) || other.person == person) && + (identical(other.description, description) || + other.description == description) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + recordType, + businessDate, + amountCents, + direction, + department, + person, + description, + createdAt, + ); + + /// Create a copy of NormalizedRecordDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NormalizedRecordDtoImplCopyWith<_$NormalizedRecordDtoImpl> get copyWith => + __$$NormalizedRecordDtoImplCopyWithImpl<_$NormalizedRecordDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$NormalizedRecordDtoImplToJson(this); + } +} + +abstract class _NormalizedRecordDto implements NormalizedRecordDto { + const factory _NormalizedRecordDto({ + required final int id, + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + required final RecordType recordType, + @JsonKey(name: 'business_date') final String businessDate, + @JsonKey(name: 'amount_cents') final int amountCents, + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + required final Direction direction, + @JsonKey(name: 'department') final String? department, + @JsonKey(name: 'person') final String? person, + @JsonKey(name: 'description') final String description, + @JsonKey(name: 'created_at') required final DateTime createdAt, + }) = _$NormalizedRecordDtoImpl; + + factory _NormalizedRecordDto.fromJson(Map json) = + _$NormalizedRecordDtoImpl.fromJson; + + @override + int get id; + @override + @JsonKey(name: 'record_type', unknownEnumValue: RecordType.unknown) + RecordType get recordType; + @override + @JsonKey(name: 'business_date') + String get businessDate; + @override + @JsonKey(name: 'amount_cents') + int get amountCents; + @override + @JsonKey(name: 'direction', unknownEnumValue: Direction.unknown) + Direction get direction; + @override + @JsonKey(name: 'department') + String? get department; + @override + @JsonKey(name: 'person') + String? get person; + @override + @JsonKey(name: 'description') + String get description; + @override + @JsonKey(name: 'created_at') + DateTime get createdAt; + + /// Create a copy of NormalizedRecordDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NormalizedRecordDtoImplCopyWith<_$NormalizedRecordDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/finance/dart/lib/src/dto/normalized_record.g.dart b/packages/finance/dart/lib/src/dto/normalized_record.g.dart new file mode 100644 index 0000000..74d18bc --- /dev/null +++ b/packages/finance/dart/lib/src/dto/normalized_record.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'normalized_record.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$NormalizedRecordDtoImpl _$$NormalizedRecordDtoImplFromJson( + Map json, +) => _$NormalizedRecordDtoImpl( + id: (json['id'] as num).toInt(), + recordType: $enumDecode( + _$RecordTypeEnumMap, + json['record_type'], + unknownValue: RecordType.unknown, + ), + businessDate: json['business_date'] as String? ?? '', + amountCents: (json['amount_cents'] as num?)?.toInt() ?? 0, + direction: $enumDecode( + _$DirectionEnumMap, + json['direction'], + unknownValue: Direction.unknown, + ), + department: json['department'] as String?, + person: json['person'] as String?, + description: json['description'] as String? ?? '', + createdAt: DateTime.parse(json['created_at'] as String), +); + +Map _$$NormalizedRecordDtoImplToJson( + _$NormalizedRecordDtoImpl instance, +) => { + 'id': instance.id, + 'record_type': _$RecordTypeEnumMap[instance.recordType]!, + 'business_date': instance.businessDate, + 'amount_cents': instance.amountCents, + 'direction': _$DirectionEnumMap[instance.direction]!, + 'department': instance.department, + 'person': instance.person, + 'description': instance.description, + 'created_at': instance.createdAt.toIso8601String(), +}; + +const _$RecordTypeEnumMap = { + RecordType.expense: 'expense', + RecordType.income: 'income', + RecordType.transfer: 'transfer', + RecordType.reimbursement: 'reimbursement', + RecordType.other: 'other', + RecordType.unknown: '__unknown__', +}; + +const _$DirectionEnumMap = { + Direction.outflow: 'outflow', + Direction.inflow: 'inflow', + Direction.unknown: '__unknown__', +}; diff --git a/packages/finance/dart/lib/src/dto/source_record.dart b/packages/finance/dart/lib/src/dto/source_record.dart new file mode 100644 index 0000000..e0f4e72 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/source_record.dart @@ -0,0 +1,27 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'enums.dart'; + +part 'source_record.freezed.dart'; +part 'source_record.g.dart'; + +@freezed +class SourceRecordDto with _$SourceRecordDto { + const factory SourceRecordDto({ + required int id, + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + required SourceType sourceType, + @JsonKey(name: 'raw_text') @Default('') String rawText, + @JsonKey(name: 'occurred_at') DateTime? occurredAt, + @JsonKey( + name: 'ingestion_status', + unknownEnumValue: IngestionStatus.unknown, + ) + @Default(IngestionStatus.pending) + IngestionStatus ingestionStatus, + @JsonKey(name: 'created_at') required DateTime createdAt, + }) = _SourceRecordDto; + + factory SourceRecordDto.fromJson(Map json) => + _$SourceRecordDtoFromJson(json); +} diff --git a/packages/finance/dart/lib/src/dto/source_record.freezed.dart b/packages/finance/dart/lib/src/dto/source_record.freezed.dart new file mode 100644 index 0000000..eb88191 --- /dev/null +++ b/packages/finance/dart/lib/src/dto/source_record.freezed.dart @@ -0,0 +1,329 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'source_record.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +SourceRecordDto _$SourceRecordDtoFromJson(Map json) { + return _SourceRecordDto.fromJson(json); +} + +/// @nodoc +mixin _$SourceRecordDto { + int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + SourceType get sourceType => throw _privateConstructorUsedError; + @JsonKey(name: 'raw_text') + String get rawText => throw _privateConstructorUsedError; + @JsonKey(name: 'occurred_at') + DateTime? get occurredAt => throw _privateConstructorUsedError; + @JsonKey(name: 'ingestion_status', unknownEnumValue: IngestionStatus.unknown) + IngestionStatus get ingestionStatus => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + DateTime get createdAt => throw _privateConstructorUsedError; + + /// Serializes this SourceRecordDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SourceRecordDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SourceRecordDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SourceRecordDtoCopyWith<$Res> { + factory $SourceRecordDtoCopyWith( + SourceRecordDto value, + $Res Function(SourceRecordDto) then, + ) = _$SourceRecordDtoCopyWithImpl<$Res, SourceRecordDto>; + @useResult + $Res call({ + int id, + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + SourceType sourceType, + @JsonKey(name: 'raw_text') String rawText, + @JsonKey(name: 'occurred_at') DateTime? occurredAt, + @JsonKey( + name: 'ingestion_status', + unknownEnumValue: IngestionStatus.unknown, + ) + IngestionStatus ingestionStatus, + @JsonKey(name: 'created_at') DateTime createdAt, + }); +} + +/// @nodoc +class _$SourceRecordDtoCopyWithImpl<$Res, $Val extends SourceRecordDto> + implements $SourceRecordDtoCopyWith<$Res> { + _$SourceRecordDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SourceRecordDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sourceType = null, + Object? rawText = null, + Object? occurredAt = freezed, + Object? ingestionStatus = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + sourceType: null == sourceType + ? _value.sourceType + : sourceType // ignore: cast_nullable_to_non_nullable + as SourceType, + rawText: null == rawText + ? _value.rawText + : rawText // ignore: cast_nullable_to_non_nullable + as String, + occurredAt: freezed == occurredAt + ? _value.occurredAt + : occurredAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + ingestionStatus: null == ingestionStatus + ? _value.ingestionStatus + : ingestionStatus // ignore: cast_nullable_to_non_nullable + as IngestionStatus, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$SourceRecordDtoImplCopyWith<$Res> + implements $SourceRecordDtoCopyWith<$Res> { + factory _$$SourceRecordDtoImplCopyWith( + _$SourceRecordDtoImpl value, + $Res Function(_$SourceRecordDtoImpl) then, + ) = __$$SourceRecordDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int id, + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + SourceType sourceType, + @JsonKey(name: 'raw_text') String rawText, + @JsonKey(name: 'occurred_at') DateTime? occurredAt, + @JsonKey( + name: 'ingestion_status', + unknownEnumValue: IngestionStatus.unknown, + ) + IngestionStatus ingestionStatus, + @JsonKey(name: 'created_at') DateTime createdAt, + }); +} + +/// @nodoc +class __$$SourceRecordDtoImplCopyWithImpl<$Res> + extends _$SourceRecordDtoCopyWithImpl<$Res, _$SourceRecordDtoImpl> + implements _$$SourceRecordDtoImplCopyWith<$Res> { + __$$SourceRecordDtoImplCopyWithImpl( + _$SourceRecordDtoImpl _value, + $Res Function(_$SourceRecordDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SourceRecordDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sourceType = null, + Object? rawText = null, + Object? occurredAt = freezed, + Object? ingestionStatus = null, + Object? createdAt = null, + }) { + return _then( + _$SourceRecordDtoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + sourceType: null == sourceType + ? _value.sourceType + : sourceType // ignore: cast_nullable_to_non_nullable + as SourceType, + rawText: null == rawText + ? _value.rawText + : rawText // ignore: cast_nullable_to_non_nullable + as String, + occurredAt: freezed == occurredAt + ? _value.occurredAt + : occurredAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + ingestionStatus: null == ingestionStatus + ? _value.ingestionStatus + : ingestionStatus // ignore: cast_nullable_to_non_nullable + as IngestionStatus, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$SourceRecordDtoImpl implements _SourceRecordDto { + const _$SourceRecordDtoImpl({ + required this.id, + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + required this.sourceType, + @JsonKey(name: 'raw_text') this.rawText = '', + @JsonKey(name: 'occurred_at') this.occurredAt, + @JsonKey( + name: 'ingestion_status', + unknownEnumValue: IngestionStatus.unknown, + ) + this.ingestionStatus = IngestionStatus.pending, + @JsonKey(name: 'created_at') required this.createdAt, + }); + + factory _$SourceRecordDtoImpl.fromJson(Map json) => + _$$SourceRecordDtoImplFromJson(json); + + @override + final int id; + @override + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + final SourceType sourceType; + @override + @JsonKey(name: 'raw_text') + final String rawText; + @override + @JsonKey(name: 'occurred_at') + final DateTime? occurredAt; + @override + @JsonKey(name: 'ingestion_status', unknownEnumValue: IngestionStatus.unknown) + final IngestionStatus ingestionStatus; + @override + @JsonKey(name: 'created_at') + final DateTime createdAt; + + @override + String toString() { + return 'SourceRecordDto(id: $id, sourceType: $sourceType, rawText: $rawText, occurredAt: $occurredAt, ingestionStatus: $ingestionStatus, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SourceRecordDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.sourceType, sourceType) || + other.sourceType == sourceType) && + (identical(other.rawText, rawText) || other.rawText == rawText) && + (identical(other.occurredAt, occurredAt) || + other.occurredAt == occurredAt) && + (identical(other.ingestionStatus, ingestionStatus) || + other.ingestionStatus == ingestionStatus) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + sourceType, + rawText, + occurredAt, + ingestionStatus, + createdAt, + ); + + /// Create a copy of SourceRecordDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SourceRecordDtoImplCopyWith<_$SourceRecordDtoImpl> get copyWith => + __$$SourceRecordDtoImplCopyWithImpl<_$SourceRecordDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$SourceRecordDtoImplToJson(this); + } +} + +abstract class _SourceRecordDto implements SourceRecordDto { + const factory _SourceRecordDto({ + required final int id, + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + required final SourceType sourceType, + @JsonKey(name: 'raw_text') final String rawText, + @JsonKey(name: 'occurred_at') final DateTime? occurredAt, + @JsonKey( + name: 'ingestion_status', + unknownEnumValue: IngestionStatus.unknown, + ) + final IngestionStatus ingestionStatus, + @JsonKey(name: 'created_at') required final DateTime createdAt, + }) = _$SourceRecordDtoImpl; + + factory _SourceRecordDto.fromJson(Map json) = + _$SourceRecordDtoImpl.fromJson; + + @override + int get id; + @override + @JsonKey(name: 'source_type', unknownEnumValue: SourceType.unknown) + SourceType get sourceType; + @override + @JsonKey(name: 'raw_text') + String get rawText; + @override + @JsonKey(name: 'occurred_at') + DateTime? get occurredAt; + @override + @JsonKey(name: 'ingestion_status', unknownEnumValue: IngestionStatus.unknown) + IngestionStatus get ingestionStatus; + @override + @JsonKey(name: 'created_at') + DateTime get createdAt; + + /// Create a copy of SourceRecordDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SourceRecordDtoImplCopyWith<_$SourceRecordDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/finance/dart/lib/src/dto/source_record.g.dart b/packages/finance/dart/lib/src/dto/source_record.g.dart new file mode 100644 index 0000000..5ab93af --- /dev/null +++ b/packages/finance/dart/lib/src/dto/source_record.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'source_record.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SourceRecordDtoImpl _$$SourceRecordDtoImplFromJson( + Map json, +) => _$SourceRecordDtoImpl( + id: (json['id'] as num).toInt(), + sourceType: $enumDecode( + _$SourceTypeEnumMap, + json['source_type'], + unknownValue: SourceType.unknown, + ), + rawText: json['raw_text'] as String? ?? '', + occurredAt: json['occurred_at'] == null + ? null + : DateTime.parse(json['occurred_at'] as String), + ingestionStatus: + $enumDecodeNullable( + _$IngestionStatusEnumMap, + json['ingestion_status'], + unknownValue: IngestionStatus.unknown, + ) ?? + IngestionStatus.pending, + createdAt: DateTime.parse(json['created_at'] as String), +); + +Map _$$SourceRecordDtoImplToJson( + _$SourceRecordDtoImpl instance, +) => { + 'id': instance.id, + 'source_type': _$SourceTypeEnumMap[instance.sourceType]!, + 'raw_text': instance.rawText, + 'occurred_at': instance.occurredAt?.toIso8601String(), + 'ingestion_status': _$IngestionStatusEnumMap[instance.ingestionStatus]!, + 'created_at': instance.createdAt.toIso8601String(), +}; + +const _$SourceTypeEnumMap = { + SourceType.image: 'image', + SourceType.chat: 'chat', + SourceType.form: 'form', + SourceType.csvRow: 'csv_row', + SourceType.bankTx: 'bank_tx', + SourceType.api: 'api', + SourceType.manual: 'manual', + SourceType.other: 'other', + SourceType.unknown: '__unknown__', +}; + +const _$IngestionStatusEnumMap = { + IngestionStatus.pending: 'pending', + IngestionStatus.parsed: 'parsed', + IngestionStatus.reviewed: 'reviewed', + IngestionStatus.failed: 'failed', + IngestionStatus.unknown: '__unknown__', +}; diff --git a/packages/finance/dart/lib/src/models/journal.dart b/packages/finance/dart/lib/src/models/journal.dart new file mode 100644 index 0000000..a16c28a --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'journal.freezed.dart'; +part 'journal.g.dart'; + +@freezed +class Journal with _$Journal { + const factory Journal({ + required String id, + required String name, + required DateTime createdAt, + }) = _Journal; + + factory Journal.fromJson(Map json) => _$JournalFromJson(json); +} diff --git a/packages/finance/dart/lib/src/models/journal.freezed.dart b/packages/finance/dart/lib/src/models/journal.freezed.dart new file mode 100644 index 0000000..8b412d0 --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal.freezed.dart @@ -0,0 +1,207 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'journal.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Journal _$JournalFromJson(Map json) { + return _Journal.fromJson(json); +} + +/// @nodoc +mixin _$Journal { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + + /// Serializes this Journal to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Journal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $JournalCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JournalCopyWith<$Res> { + factory $JournalCopyWith(Journal value, $Res Function(Journal) then) = + _$JournalCopyWithImpl<$Res, Journal>; + @useResult + $Res call({String id, String name, DateTime createdAt}); +} + +/// @nodoc +class _$JournalCopyWithImpl<$Res, $Val extends Journal> + implements $JournalCopyWith<$Res> { + _$JournalCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Journal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$JournalImplCopyWith<$Res> implements $JournalCopyWith<$Res> { + factory _$$JournalImplCopyWith( + _$JournalImpl value, + $Res Function(_$JournalImpl) then, + ) = __$$JournalImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, DateTime createdAt}); +} + +/// @nodoc +class __$$JournalImplCopyWithImpl<$Res> + extends _$JournalCopyWithImpl<$Res, _$JournalImpl> + implements _$$JournalImplCopyWith<$Res> { + __$$JournalImplCopyWithImpl( + _$JournalImpl _value, + $Res Function(_$JournalImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Journal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? createdAt = null, + }) { + return _then( + _$JournalImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$JournalImpl implements _Journal { + const _$JournalImpl({ + required this.id, + required this.name, + required this.createdAt, + }); + + factory _$JournalImpl.fromJson(Map json) => + _$$JournalImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + final DateTime createdAt; + + @override + String toString() { + return 'Journal(id: $id, name: $name, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JournalImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, createdAt); + + /// Create a copy of Journal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$JournalImplCopyWith<_$JournalImpl> get copyWith => + __$$JournalImplCopyWithImpl<_$JournalImpl>(this, _$identity); + + @override + Map toJson() { + return _$$JournalImplToJson(this); + } +} + +abstract class _Journal implements Journal { + const factory _Journal({ + required final String id, + required final String name, + required final DateTime createdAt, + }) = _$JournalImpl; + + factory _Journal.fromJson(Map json) = _$JournalImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + DateTime get createdAt; + + /// Create a copy of Journal + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$JournalImplCopyWith<_$JournalImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/finance/dart/lib/src/models/journal.g.dart b/packages/finance/dart/lib/src/models/journal.g.dart new file mode 100644 index 0000000..02d7c80 --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'journal.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$JournalImpl _$$JournalImplFromJson(Map json) => + _$JournalImpl( + id: json['id'] as String, + name: json['name'] as String, + createdAt: DateTime.parse(json['createdAt'] as String), + ); + +Map _$$JournalImplToJson(_$JournalImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'createdAt': instance.createdAt.toIso8601String(), + }; diff --git a/packages/finance/dart/lib/src/models/journal_entry.dart b/packages/finance/dart/lib/src/models/journal_entry.dart new file mode 100644 index 0000000..b7609c6 --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal_entry.dart @@ -0,0 +1,39 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'journal_entry.freezed.dart'; +part 'journal_entry.g.dart'; + +@JsonEnum() +enum LineType { debit, credit } + +@freezed +class JournalEntryLine with _$JournalEntryLine { + const JournalEntryLine._(); + + @Assert('amount >= 0', 'amount must be non-negative') + const factory JournalEntryLine({ + required String id, + required LineType type, + @Default(0) double amount, + @Default('') String description, + required DateTime createdAt, + }) = _JournalEntryLine; + + factory JournalEntryLine.fromJson(Map json) => _$JournalEntryLineFromJson(json); +} + +@freezed +class JournalEntry with _$JournalEntry { + const factory JournalEntry({ + required String id, + required String journalId, + required DateTime createdAt, + @Default('') String description, + @JsonKey(toJson: _linesToJson) @Default([]) List lines, + }) = _JournalEntry; + + factory JournalEntry.fromJson(Map json) => _$JournalEntryFromJson(json); +} + +List> _linesToJson(List lines) => + lines.map((l) => l.toJson()).toList(); diff --git a/packages/finance/dart/lib/src/models/journal_entry.freezed.dart b/packages/finance/dart/lib/src/models/journal_entry.freezed.dart new file mode 100644 index 0000000..20d268e --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal_entry.freezed.dart @@ -0,0 +1,532 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'journal_entry.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +JournalEntryLine _$JournalEntryLineFromJson(Map json) { + return _JournalEntryLine.fromJson(json); +} + +/// @nodoc +mixin _$JournalEntryLine { + String get id => throw _privateConstructorUsedError; + LineType get type => throw _privateConstructorUsedError; + double get amount => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + + /// Serializes this JournalEntryLine to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of JournalEntryLine + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $JournalEntryLineCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JournalEntryLineCopyWith<$Res> { + factory $JournalEntryLineCopyWith( + JournalEntryLine value, + $Res Function(JournalEntryLine) then, + ) = _$JournalEntryLineCopyWithImpl<$Res, JournalEntryLine>; + @useResult + $Res call({ + String id, + LineType type, + double amount, + String description, + DateTime createdAt, + }); +} + +/// @nodoc +class _$JournalEntryLineCopyWithImpl<$Res, $Val extends JournalEntryLine> + implements $JournalEntryLineCopyWith<$Res> { + _$JournalEntryLineCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of JournalEntryLine + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? type = null, + Object? amount = null, + Object? description = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as LineType, + amount: null == amount + ? _value.amount + : amount // ignore: cast_nullable_to_non_nullable + as double, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$JournalEntryLineImplCopyWith<$Res> + implements $JournalEntryLineCopyWith<$Res> { + factory _$$JournalEntryLineImplCopyWith( + _$JournalEntryLineImpl value, + $Res Function(_$JournalEntryLineImpl) then, + ) = __$$JournalEntryLineImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + LineType type, + double amount, + String description, + DateTime createdAt, + }); +} + +/// @nodoc +class __$$JournalEntryLineImplCopyWithImpl<$Res> + extends _$JournalEntryLineCopyWithImpl<$Res, _$JournalEntryLineImpl> + implements _$$JournalEntryLineImplCopyWith<$Res> { + __$$JournalEntryLineImplCopyWithImpl( + _$JournalEntryLineImpl _value, + $Res Function(_$JournalEntryLineImpl) _then, + ) : super(_value, _then); + + /// Create a copy of JournalEntryLine + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? type = null, + Object? amount = null, + Object? description = null, + Object? createdAt = null, + }) { + return _then( + _$JournalEntryLineImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as LineType, + amount: null == amount + ? _value.amount + : amount // ignore: cast_nullable_to_non_nullable + as double, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$JournalEntryLineImpl extends _JournalEntryLine { + const _$JournalEntryLineImpl({ + required this.id, + required this.type, + this.amount = 0, + this.description = '', + required this.createdAt, + }) : assert(amount >= 0, 'amount must be non-negative'), + super._(); + + factory _$JournalEntryLineImpl.fromJson(Map json) => + _$$JournalEntryLineImplFromJson(json); + + @override + final String id; + @override + final LineType type; + @override + @JsonKey() + final double amount; + @override + @JsonKey() + final String description; + @override + final DateTime createdAt; + + @override + String toString() { + return 'JournalEntryLine(id: $id, type: $type, amount: $amount, description: $description, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JournalEntryLineImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.type, type) || other.type == type) && + (identical(other.amount, amount) || other.amount == amount) && + (identical(other.description, description) || + other.description == description) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, id, type, amount, description, createdAt); + + /// Create a copy of JournalEntryLine + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$JournalEntryLineImplCopyWith<_$JournalEntryLineImpl> get copyWith => + __$$JournalEntryLineImplCopyWithImpl<_$JournalEntryLineImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$JournalEntryLineImplToJson(this); + } +} + +abstract class _JournalEntryLine extends JournalEntryLine { + const factory _JournalEntryLine({ + required final String id, + required final LineType type, + final double amount, + final String description, + required final DateTime createdAt, + }) = _$JournalEntryLineImpl; + const _JournalEntryLine._() : super._(); + + factory _JournalEntryLine.fromJson(Map json) = + _$JournalEntryLineImpl.fromJson; + + @override + String get id; + @override + LineType get type; + @override + double get amount; + @override + String get description; + @override + DateTime get createdAt; + + /// Create a copy of JournalEntryLine + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$JournalEntryLineImplCopyWith<_$JournalEntryLineImpl> get copyWith => + throw _privateConstructorUsedError; +} + +JournalEntry _$JournalEntryFromJson(Map json) { + return _JournalEntry.fromJson(json); +} + +/// @nodoc +mixin _$JournalEntry { + String get id => throw _privateConstructorUsedError; + String get journalId => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + @JsonKey(toJson: _linesToJson) + List get lines => throw _privateConstructorUsedError; + + /// Serializes this JournalEntry to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of JournalEntry + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $JournalEntryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $JournalEntryCopyWith<$Res> { + factory $JournalEntryCopyWith( + JournalEntry value, + $Res Function(JournalEntry) then, + ) = _$JournalEntryCopyWithImpl<$Res, JournalEntry>; + @useResult + $Res call({ + String id, + String journalId, + DateTime createdAt, + String description, + @JsonKey(toJson: _linesToJson) List lines, + }); +} + +/// @nodoc +class _$JournalEntryCopyWithImpl<$Res, $Val extends JournalEntry> + implements $JournalEntryCopyWith<$Res> { + _$JournalEntryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of JournalEntry + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? journalId = null, + Object? createdAt = null, + Object? description = null, + Object? lines = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + journalId: null == journalId + ? _value.journalId + : journalId // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + lines: null == lines + ? _value.lines + : lines // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$JournalEntryImplCopyWith<$Res> + implements $JournalEntryCopyWith<$Res> { + factory _$$JournalEntryImplCopyWith( + _$JournalEntryImpl value, + $Res Function(_$JournalEntryImpl) then, + ) = __$$JournalEntryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String journalId, + DateTime createdAt, + String description, + @JsonKey(toJson: _linesToJson) List lines, + }); +} + +/// @nodoc +class __$$JournalEntryImplCopyWithImpl<$Res> + extends _$JournalEntryCopyWithImpl<$Res, _$JournalEntryImpl> + implements _$$JournalEntryImplCopyWith<$Res> { + __$$JournalEntryImplCopyWithImpl( + _$JournalEntryImpl _value, + $Res Function(_$JournalEntryImpl) _then, + ) : super(_value, _then); + + /// Create a copy of JournalEntry + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? journalId = null, + Object? createdAt = null, + Object? description = null, + Object? lines = null, + }) { + return _then( + _$JournalEntryImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + journalId: null == journalId + ? _value.journalId + : journalId // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + lines: null == lines + ? _value._lines + : lines // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$JournalEntryImpl implements _JournalEntry { + const _$JournalEntryImpl({ + required this.id, + required this.journalId, + required this.createdAt, + this.description = '', + @JsonKey(toJson: _linesToJson) + final List lines = const [], + }) : _lines = lines; + + factory _$JournalEntryImpl.fromJson(Map json) => + _$$JournalEntryImplFromJson(json); + + @override + final String id; + @override + final String journalId; + @override + final DateTime createdAt; + @override + @JsonKey() + final String description; + final List _lines; + @override + @JsonKey(toJson: _linesToJson) + List get lines { + if (_lines is EqualUnmodifiableListView) return _lines; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_lines); + } + + @override + String toString() { + return 'JournalEntry(id: $id, journalId: $journalId, createdAt: $createdAt, description: $description, lines: $lines)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$JournalEntryImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.journalId, journalId) || + other.journalId == journalId) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality().equals(other._lines, _lines)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + journalId, + createdAt, + description, + const DeepCollectionEquality().hash(_lines), + ); + + /// Create a copy of JournalEntry + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$JournalEntryImplCopyWith<_$JournalEntryImpl> get copyWith => + __$$JournalEntryImplCopyWithImpl<_$JournalEntryImpl>(this, _$identity); + + @override + Map toJson() { + return _$$JournalEntryImplToJson(this); + } +} + +abstract class _JournalEntry implements JournalEntry { + const factory _JournalEntry({ + required final String id, + required final String journalId, + required final DateTime createdAt, + final String description, + @JsonKey(toJson: _linesToJson) final List lines, + }) = _$JournalEntryImpl; + + factory _JournalEntry.fromJson(Map json) = + _$JournalEntryImpl.fromJson; + + @override + String get id; + @override + String get journalId; + @override + DateTime get createdAt; + @override + String get description; + @override + @JsonKey(toJson: _linesToJson) + List get lines; + + /// Create a copy of JournalEntry + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$JournalEntryImplCopyWith<_$JournalEntryImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/finance/dart/lib/src/models/journal_entry.g.dart b/packages/finance/dart/lib/src/models/journal_entry.g.dart new file mode 100644 index 0000000..7d255f3 --- /dev/null +++ b/packages/finance/dart/lib/src/models/journal_entry.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'journal_entry.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$JournalEntryLineImpl _$$JournalEntryLineImplFromJson( + Map json, +) => _$JournalEntryLineImpl( + id: json['id'] as String, + type: $enumDecode(_$LineTypeEnumMap, json['type']), + amount: (json['amount'] as num?)?.toDouble() ?? 0, + description: json['description'] as String? ?? '', + createdAt: DateTime.parse(json['createdAt'] as String), +); + +Map _$$JournalEntryLineImplToJson( + _$JournalEntryLineImpl instance, +) => { + 'id': instance.id, + 'type': _$LineTypeEnumMap[instance.type]!, + 'amount': instance.amount, + 'description': instance.description, + 'createdAt': instance.createdAt.toIso8601String(), +}; + +const _$LineTypeEnumMap = {LineType.debit: 'debit', LineType.credit: 'credit'}; + +_$JournalEntryImpl _$$JournalEntryImplFromJson(Map json) => + _$JournalEntryImpl( + id: json['id'] as String, + journalId: json['journalId'] as String, + createdAt: DateTime.parse(json['createdAt'] as String), + description: json['description'] as String? ?? '', + lines: + (json['lines'] as List?) + ?.map((e) => JournalEntryLine.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$$JournalEntryImplToJson(_$JournalEntryImpl instance) => + { + 'id': instance.id, + 'journalId': instance.journalId, + 'createdAt': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'lines': _linesToJson(instance.lines), + }; diff --git a/packages/finance/dart/pubspec.yaml b/packages/finance/dart/pubspec.yaml new file mode 100644 index 0000000..4606860 --- /dev/null +++ b/packages/finance/dart/pubspec.yaml @@ -0,0 +1,20 @@ +name: quanttide_finance +description: 量潮金融领域模型 — 核心实体(Journal、JournalEntry) +version: 0.2.0 +homepage: https://github.com/quanttide/quanttide-finance-toolkit +repository: https://github.com/quanttide/quanttide-finance-toolkit +issue_tracker: https://github.com/quanttide/quanttide-finance-toolkit/issues + +environment: + sdk: ^3.11.5 + +dependencies: + freezed_annotation: ^3.1.0 + json_annotation: ^4.9.0 + +dev_dependencies: + freezed: ^3.2.5 + json_serializable: ^6.9.0 + build_runner: ^2.4.6 + lints: ^6.0.0 + test: ^1.25.6 diff --git a/packages/finance/dart/run_build_runner.sh b/packages/finance/dart/run_build_runner.sh new file mode 100755 index 0000000..dd0e8ad --- /dev/null +++ b/packages/finance/dart/run_build_runner.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Add Flutter to PATH if available +[ -d "$HOME/flutter/bin" ] && PATH="$HOME/flutter/bin:$PATH" +cd "$(cd "$(dirname "$0")" && pwd)" +dart pub get 2>&1 | tail -3 +dart run build_runner build --delete-conflicting-outputs 2>&1 diff --git a/packages/finance/dart/run_test.sh b/packages/finance/dart/run_test.sh new file mode 100755 index 0000000..c2b1787 --- /dev/null +++ b/packages/finance/dart/run_test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Add Flutter to PATH if available +[ -d "$HOME/flutter/bin" ] && PATH="$HOME/flutter/bin:$PATH" +cd "$(cd "$(dirname "$0")" && pwd)" +dart test 2>&1 diff --git a/packages/finance/dart/test/dto/classification_result_test.dart b/packages/finance/dart/test/dto/classification_result_test.dart new file mode 100644 index 0000000..07f071a --- /dev/null +++ b/packages/finance/dart/test/dto/classification_result_test.dart @@ -0,0 +1,136 @@ +import 'package:test/test.dart'; +import 'package:quanttide_finance/quanttide_finance.dart'; + +void main() { + group('ClassificationResultDto fromJson round-trip', () { + test('parses full response', () { + final json = { + 'id': 1, + 'normalized_record_id': 42, + 'taxonomy': 'expense_type', + 'category': 'office_supplies', + 'tags': {'brand': 'Staples'}, + 'classifier_kind': 'ai', + 'confidence': 0.95, + 'model_version': 'gpt-4o-2026-05-01', + 'review_status': 'candidate', + 'is_active': true, + 'created_at': '2026-06-01T12:00:00Z', + 'updated_at': '2026-06-01T12:30:00Z', + }; + + final dto = ClassificationResultDto.fromJson(json); + expect(dto.id, 1); + expect(dto.normalizedRecordId, 42); + expect(dto.taxonomy, 'expense_type'); + expect(dto.category, 'office_supplies'); + expect(dto.tags, {'brand': 'Staples'}); + expect(dto.classifierKind, ClassifierKind.ai); + expect(dto.confidence, 0.95); + expect(dto.modelVersion, 'gpt-4o-2026-05-01'); + expect(dto.reviewStatus, ReviewStatus.candidate); + expect(dto.isActive, true); + }); + + test('toJson uses snake_case keys', () { + final dto = ClassificationResultDto( + id: 1, + normalizedRecordId: 42, + taxonomy: 'expense_type', + category: 'office_supplies', + classifierKind: ClassifierKind.manual, + reviewStatus: ReviewStatus.accepted, + isActive: true, + createdAt: DateTime(2026, 1, 1), + updatedAt: DateTime(2026, 1, 1), + ); + + final json = dto.toJson(); + expect(json['normalized_record_id'], 42); + expect(json['classifier_kind'], 'manual'); + expect(json['review_status'], 'accepted'); + expect(json['is_active'], true); + expect(json['created_at'], isA()); + expect(json['updated_at'], isA()); + }); + + test('round-trip preserves all fields', () { + final original = ClassificationResultDto( + id: 1, + normalizedRecordId: 42, + taxonomy: 'expense_type', + category: 'office_supplies', + tags: {'brand': 'Staples'}, + classifierKind: ClassifierKind.ai, + confidence: 0.95, + modelVersion: 'gpt-4o-2026-05-01', + reviewStatus: ReviewStatus.candidate, + isActive: true, + createdAt: DateTime(2026, 1, 1), + updatedAt: DateTime(2026, 1, 1, 0, 30), + ); + + final json = original.toJson(); + final restored = ClassificationResultDto.fromJson(json); + expect(restored, original); + }); + }); + + group('ClassificationResultDto defaults', () { + test('review_status defaults to candidate', () { + const json = { + 'id': 1, + 'normalized_record_id': 1, + 'taxonomy': 'expense_type', + 'category': 'office_supplies', + 'classifier_kind': 'manual', + 'created_at': '2026-01-01T00:00:00Z', + 'updated_at': '2026-01-01T00:00:00Z', + }; + final dto = ClassificationResultDto.fromJson(json); + expect(dto.reviewStatus, ReviewStatus.candidate); + expect(dto.isActive, true); + }); + + test('unknown enum values fall back to unknown', () { + const json = { + 'id': 1, + 'normalized_record_id': 1, + 'taxonomy': 'expense_type', + 'category': 'office_supplies', + 'classifier_kind': 'unknown_classifier', + 'review_status': 'unknown_status', + 'is_active': true, + 'created_at': '2026-01-01T00:00:00Z', + 'updated_at': '2026-01-01T00:00:00Z', + }; + final dto = ClassificationResultDto.fromJson(json); + expect(dto.classifierKind, ClassifierKind.unknown); + expect(dto.reviewStatus, ReviewStatus.unknown); + }); + }); + + group('ClassificationResultDto copyWith', () { + test('copies with modified fields', () { + final dto = ClassificationResultDto( + id: 1, + normalizedRecordId: 42, + taxonomy: 'expense_type', + category: 'office_supplies', + classifierKind: ClassifierKind.ai, + reviewStatus: ReviewStatus.candidate, + isActive: true, + createdAt: DateTime(2026, 1, 1), + updatedAt: DateTime(2026, 1, 1), + ); + + final updated = dto.copyWith( + reviewStatus: ReviewStatus.accepted, + isActive: false, + ); + expect(updated.reviewStatus, ReviewStatus.accepted); + expect(updated.isActive, false); + expect(updated.id, 1); // unchanged + }); + }); +} diff --git a/packages/finance/dart/test/dto/enums_test.dart b/packages/finance/dart/test/dto/enums_test.dart new file mode 100644 index 0000000..1153289 --- /dev/null +++ b/packages/finance/dart/test/dto/enums_test.dart @@ -0,0 +1,207 @@ +import 'package:test/test.dart'; +import 'package:quanttide_finance/quanttide_finance.dart'; + +/// Wire-value alignment tests. +/// +/// Each @JsonValue label is verified against doc/entities.md wire values +/// through DTO toJson serialization. +void main() { + group('SourceType wire values', () { + for (final entry in { + 'image': SourceType.image, + 'chat': SourceType.chat, + 'form': SourceType.form, + 'csv_row': SourceType.csvRow, + 'bank_tx': SourceType.bankTx, + 'api': SourceType.api, + 'manual': SourceType.manual, + 'other': SourceType.other, + }.entries) { + test('${entry.key}', () { + final dto = SourceRecordDto( + id: 1, + sourceType: entry.value, + rawText: '', + occurredAt: null, + ingestionStatus: IngestionStatus.pending, + createdAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['source_type'], + equals(entry.key), + reason: + 'SourceType.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); + + group('IngestionStatus wire values', () { + for (final entry in { + 'pending': IngestionStatus.pending, + 'parsed': IngestionStatus.parsed, + 'reviewed': IngestionStatus.reviewed, + 'failed': IngestionStatus.failed, + }.entries) { + test('${entry.key}', () { + final dto = SourceRecordDto( + id: 1, + sourceType: SourceType.manual, + rawText: '', + occurredAt: null, + ingestionStatus: entry.value, + createdAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['ingestion_status'], + equals(entry.key), + reason: + 'IngestionStatus.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); + + group('RecordType wire values', () { + for (final entry in { + 'expense': RecordType.expense, + 'income': RecordType.income, + 'transfer': RecordType.transfer, + 'reimbursement': RecordType.reimbursement, + 'other': RecordType.other, + }.entries) { + test('${entry.key}', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: entry.value, + businessDate: '2026-06-01', + amountCents: 0, + direction: Direction.outflow, + department: null, + person: null, + description: '', + createdAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['record_type'], + equals(entry.key), + reason: + 'RecordType.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); + + group('Direction wire values', () { + for (final entry in { + 'outflow': Direction.outflow, + 'inflow': Direction.inflow, + }.entries) { + test('${entry.key}', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: RecordType.expense, + businessDate: '2026-06-01', + amountCents: 0, + direction: entry.value, + department: null, + person: null, + description: '', + createdAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['direction'], + equals(entry.key), + reason: + 'Direction.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); + + group('SourceType', () { + test('unknown fallback', () { + const json = { + 'id': 1, + 'source_type': 'invalid', + 'created_at': '2026-01-01T00:00:00Z', + }; + final dto = SourceRecordDto.fromJson(json); + expect(dto.sourceType, SourceType.unknown); + }); + }); + + group('IngestionStatus', () { + test('default value', () { + const json = { + 'id': 1, + 'source_type': 'manual', + 'created_at': '2026-01-01T00:00:00Z', + }; + final dto = SourceRecordDto.fromJson(json); + expect(dto.ingestionStatus, IngestionStatus.pending); + }); + }); + + group('ClassifierKind wire values', () { + for (final entry in { + 'ai': ClassifierKind.ai, + 'rule': ClassifierKind.rule, + 'manual': ClassifierKind.manual, + }.entries) { + test(entry.key, () { + final dto = ClassificationResultDto( + id: 1, + normalizedRecordId: 1, + taxonomy: 'expense_type', + category: 'office_supplies', + classifierKind: entry.value, + reviewStatus: ReviewStatus.candidate, + isActive: true, + createdAt: DateTime(2026, 1, 1), + updatedAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['classifier_kind'], + equals(entry.key), + reason: + 'ClassifierKind.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); + + group('ReviewStatus wire values', () { + for (final entry in { + 'candidate': ReviewStatus.candidate, + 'accepted': ReviewStatus.accepted, + 'rejected': ReviewStatus.rejected, + }.entries) { + test(entry.key, () { + final dto = ClassificationResultDto( + id: 1, + normalizedRecordId: 1, + taxonomy: 'expense_type', + category: 'office_supplies', + classifierKind: ClassifierKind.manual, + reviewStatus: entry.value, + isActive: true, + createdAt: DateTime(2026, 1, 1), + updatedAt: DateTime(2026, 1, 1), + ); + final json = dto.toJson(); + expect( + json['review_status'], + equals(entry.key), + reason: + 'ReviewStatus.${entry.value.name} @JsonValue must match doc/entities.md', + ); + }); + } + }); +} diff --git a/packages/finance/dart/test/dto/normalized_record_test.dart b/packages/finance/dart/test/dto/normalized_record_test.dart new file mode 100644 index 0000000..f154aaf --- /dev/null +++ b/packages/finance/dart/test/dto/normalized_record_test.dart @@ -0,0 +1,93 @@ +import 'package:test/test.dart'; +import 'package:quanttide_finance/quanttide_finance.dart'; + +void main() { + group('NormalizedRecordDto', () { + test('toJson uses snake_case keys', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: RecordType.expense, + businessDate: '2026-06-01', + amountCents: 120000, + direction: Direction.outflow, + department: '研发部', + person: '张三', + description: '办公用品采购', + createdAt: DateTime(2026, 6, 1), + ); + final json = dto.toJson(); + expect(json.containsKey('record_type'), isTrue); + expect(json.containsKey('business_date'), isTrue); + expect(json.containsKey('amount_cents'), isTrue); + expect(json.containsKey('created_at'), isTrue); + expect(json.containsKey('recordType'), isFalse); + expect(json.containsKey('amountCents'), isFalse); + }); + + test('fromJson round-trip', () { + final dto = NormalizedRecordDto( + id: 42, + recordType: RecordType.income, + businessDate: '2026-06-15', + amountCents: 500000, + direction: Direction.inflow, + department: null, + person: null, + description: '客户回款', + createdAt: DateTime(2026, 6, 15), + ); + expect(NormalizedRecordDto.fromJson(dto.toJson()), dto); + }); + + test('copyWith', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: RecordType.expense, + businessDate: '2026-06-01', + amountCents: 1000, + direction: Direction.outflow, + department: null, + person: null, + description: 'test', + createdAt: DateTime(2026, 6, 1), + ); + final updated = dto.copyWith(amountCents: 2000); + expect(updated.amountCents, 2000); + expect(updated.id, dto.id); + }); + + test('nullable fields', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: RecordType.other, + businessDate: '2026-06-01', + amountCents: 0, + direction: Direction.outflow, + department: null, + person: null, + description: '', + createdAt: DateTime(2026, 6, 1), + ); + final json = dto.toJson(); + expect(json['department'], isNull); + expect(json['person'], isNull); + }); + + test('businessDate is display-only string', () { + final dto = NormalizedRecordDto( + id: 1, + recordType: RecordType.expense, + businessDate: '2026-06-01', + amountCents: 0, + direction: Direction.outflow, + department: null, + person: null, + description: '', + createdAt: DateTime(2026, 6, 1), + ); + final json = dto.toJson(); + expect(json['business_date'], isA()); + expect(json['business_date'], '2026-06-01'); + }); + }); +} diff --git a/packages/finance/dart/test/dto/source_record_test.dart b/packages/finance/dart/test/dto/source_record_test.dart new file mode 100644 index 0000000..528b1ca --- /dev/null +++ b/packages/finance/dart/test/dto/source_record_test.dart @@ -0,0 +1,64 @@ +import 'package:test/test.dart'; +import 'package:quanttide_finance/quanttide_finance.dart'; + +void main() { + group('SourceRecordDto', () { + test('toJson uses snake_case keys', () { + final dto = SourceRecordDto( + id: 1, + sourceType: SourceType.csvRow, + rawText: '2026-06-01,办公用品,120000,outflow', + occurredAt: null, + ingestionStatus: IngestionStatus.parsed, + createdAt: DateTime(2026, 6, 1), + ); + final json = dto.toJson(); + expect(json.containsKey('source_type'), isTrue); + expect(json.containsKey('raw_text'), isTrue); + expect(json.containsKey('ingestion_status'), isTrue); + expect(json.containsKey('created_at'), isTrue); + // Ensure no camelCase keys leak through + expect(json.containsKey('sourceType'), isFalse); + expect(json.containsKey('rawText'), isFalse); + }); + + test('fromJson round-trip', () { + final dto = SourceRecordDto( + id: 42, + sourceType: SourceType.manual, + rawText: '购买办公用品', + occurredAt: DateTime(2026, 5, 30), + ingestionStatus: IngestionStatus.pending, + createdAt: DateTime(2026, 5, 31), + ); + expect(SourceRecordDto.fromJson(dto.toJson()), dto); + }); + + test('copyWith', () { + final dto = SourceRecordDto( + id: 1, + sourceType: SourceType.other, + rawText: '', + occurredAt: null, + ingestionStatus: IngestionStatus.pending, + createdAt: DateTime(2026, 6, 1), + ); + final updated = dto.copyWith(ingestionStatus: IngestionStatus.reviewed); + expect(updated.ingestionStatus, IngestionStatus.reviewed); + expect(updated.id, dto.id); // unchanged + }); + + test('nullable fields', () { + final dto = SourceRecordDto( + id: 1, + sourceType: SourceType.form, + rawText: 'test', + occurredAt: null, + ingestionStatus: IngestionStatus.pending, + createdAt: DateTime(2026, 6, 1), + ); + final json = dto.toJson(); + expect(json['occurred_at'], isNull); + }); + }); +} diff --git a/packages/finance/dart/test/models/journal_test.dart b/packages/finance/dart/test/models/journal_test.dart new file mode 100644 index 0000000..43c22ff --- /dev/null +++ b/packages/finance/dart/test/models/journal_test.dart @@ -0,0 +1,34 @@ +import 'package:test/test.dart'; +import 'package:quanttide_finance/quanttide_finance.dart'; + +void main() { + test('Journal toJson / fromJson', () { + final j = Journal(id: '1', name: '备用金', createdAt: DateTime(2026, 5, 29)); + expect(Journal.fromJson(j.toJson()), j); + }); + + test('JournalEntryLine toJson / fromJson', () { + final l = JournalEntryLine( + id: 'l1', type: LineType.debit, amount: 1000, + description: '买纸', createdAt: DateTime(2026, 5, 29), + ); + expect(JournalEntryLine.fromJson(l.toJson()), l); + }); + + test('JournalEntry toJson / fromJson', () { + final e = JournalEntry( + id: 'je1', journalId: 'j1', createdAt: DateTime(2026, 5, 29), + description: '采购', + lines: [ + JournalEntryLine(id: 'l1', type: LineType.debit, amount: 1200, createdAt: DateTime(2026, 5, 29)), + JournalEntryLine(id: 'l2', type: LineType.credit, amount: 1200, createdAt: DateTime(2026, 5, 29)), + ], + ); + expect(JournalEntry.fromJson(e.toJson()), e); + }); + + test('copyWith', () { + final j = Journal(id: '1', name: '备用金', createdAt: DateTime(2026, 5, 29)); + expect(j.copyWith(name: '改名的备用金').name, '改名的备用金'); + }); +}