LCOV - code coverage report
Current view: top level - lib/_classes/storage - file_picker.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 53 0.0 %
Date: 2024-10-04 11:12:13 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2023 The terCAD team. All rights reserved.
       2             : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
       3             : 
       4             : import 'package:app_finance/_classes/herald/app_locale.dart';
       5             : import 'package:app_finance/_classes/storage/file_parser.dart';
       6             : import 'package:app_finance/_mixins/file/file_import_mixin.dart';
       7             : import 'package:csv/csv.dart';
       8             : import 'package:xml/xml.dart';
       9             : 
      10             : typedef FileScope = List<List<dynamic>>;
      11             : 
      12             : class FilePicker with FileImportMixin {
      13             :   static const csvFormat = 'csv';
      14             :   static const qifFormat = 'qif';
      15             :   static const ofxFormat = 'ofx';
      16             :   static const fileFormats = [csvFormat, qifFormat, ofxFormat];
      17             :   final List<String> ext;
      18             :   List<String> columnMap = [
      19             :     FileParser.attrBillUuid,
      20             :     FileParser.attrBillAmount,
      21             :     FileParser.attrBillComment,
      22             :     FileParser.attrCategoryName,
      23             :     FileParser.attrBillDate,
      24             :     FileParser.attrBillType,
      25             :   ];
      26             :   final header = [
      27             :     AppLocale.labels.uuid,
      28             :     AppLocale.labels.expense,
      29             :     AppLocale.labels.description,
      30             :     AppLocale.labels.budget,
      31             :     AppLocale.labels.balanceDate,
      32             :     AppLocale.labels.flowTypeTooltip,
      33             :   ];
      34             : 
      35           0 :   FilePicker(this.ext);
      36             : 
      37           0 :   FileScope _parseCsv(String content, String splitter) {
      38           0 :     final result = CsvToListConverter(eol: splitter).convert(content);
      39           0 :     columnMap = List<String>.filled(result.first.length, '');
      40             :     return result;
      41             :   }
      42             : 
      43           0 :   FileScope _parseQif(String content, String splitter) {
      44           0 :     final scope = content.split(splitter);
      45             :     int idx = 1;
      46           0 :     Map<String, int> mapping = {
      47             :       'N': 0, // "Number" for the transaction
      48             :       'T': 1, // "Total" amount
      49             :       'P': 2, // "Payee"
      50             :       'L': 3, // "Category/Account Line"
      51             :       'D': 4, // "Date"
      52             :     };
      53             :     int billType = 5;
      54           0 :     FileScope result = [header];
      55           0 :     result.add(List<dynamic>.filled(header.length, null));
      56           0 :     for (int i = 0; i < scope.length; i++) {
      57           0 :       if (scope[i].isEmpty) {
      58             :         continue;
      59             :       }
      60           0 :       final key = scope[i].substring(0, 1);
      61           0 :       final value = scope[i].substring(1);
      62           0 :       if (key == '^') {
      63           0 :         idx++;
      64           0 :         result.add(List<dynamic>.filled(header.length, null));
      65             :         continue;
      66             :       }
      67           0 :       int? pos = mapping[key];
      68             :       if (pos != null) {
      69           0 :         if (key == 'T') {
      70           0 :           result[idx][pos] = (double.tryParse(value) ?? 0.0).abs().toString();
      71           0 :           result[idx][billType] = (double.tryParse(value) ?? 0) > 0 ? AppLocale.labels.flowTypeInvoice : '';
      72             :         } else {
      73           0 :           result[idx][pos] = value;
      74             :         }
      75             :       }
      76             :     }
      77           0 :     if (result.last.every((element) => element == null)) {
      78           0 :       result.removeLast();
      79             :     }
      80             :     return result;
      81             :   }
      82             : 
      83           0 :   FileScope _parseOfx(String content) {
      84           0 :     FileScope result = [header];
      85           0 :     final xmlData = XmlDocument.parse(content);
      86             :     int amountType = 1;
      87             :     int dateType = 4;
      88             :     int billType = 5;
      89           0 :     Map<String, int> mapping = {
      90             :       'FITID': 0, // Unique ID
      91             :       'TRNAMT': amountType, // Amount
      92             :       'NAME': 2, // Description
      93             :       'DTPOSTED': dateType, // Date
      94             :     };
      95           0 :     for (XmlElement node in xmlData.findAllElements('STMTTRN')) {
      96           0 :       final tmp = List<dynamic>.filled(6, null);
      97           0 :       for (XmlElement element in node.childElements) {
      98           0 :         int? pos = mapping[element.name.toString()];
      99           0 :         String value = element.innerText;
     100             :         if (pos != null) {
     101           0 :           if (pos == dateType) {
     102           0 :             List<String> date = value.split('');
     103           0 :             date.insert(8, 'T');
     104           0 :             tmp[pos] = date.join('');
     105           0 :           } else if (pos == amountType) {
     106           0 :             double nm = double.tryParse(value) ?? 0.0;
     107           0 :             tmp[pos] = nm.abs().toString();
     108           0 :             tmp[billType] = nm > 0 ? AppLocale.labels.flowTypeInvoice : '';
     109             :           } else {
     110           0 :             tmp[pos] = value;
     111             :           }
     112             :         }
     113             :       }
     114           0 :       result.add(tmp);
     115             :     }
     116             :     return result;
     117             :   }
     118             : 
     119           0 :   Future<FileScope?> pickFile() async {
     120           0 :     String? content = await importFile(ext);
     121             :     if (content != null) {
     122           0 :       final splitter = content.contains('\r\n') ? '\r\n' : '\n';
     123           0 :       switch (ext.first) {
     124           0 :         case csvFormat:
     125           0 :           return _parseCsv(content, splitter);
     126           0 :         case qifFormat:
     127           0 :           return _parseQif(content, splitter);
     128           0 :         case ofxFormat:
     129           0 :           return _parseOfx(content);
     130             :       }
     131             :     } else {
     132           0 :       throw Exception(AppLocale.labels.missingContent);
     133             :     }
     134             :     return null;
     135             :   }
     136             : }

Generated by: LCOV version 1.14