public override bool analyzeTransaction(Transaction t, byte[] data) { // 日付 int value = read4b(data, 4); if (value == 0 && data[0] == 0) { return false; // おそらく空エントリ } t.date = new DateTime(2000, 1, 1); t.date += TimeSpan.FromDays(value >> 17); t.date += TimeSpan.FromSeconds(value & 0x1ffff); // 金額 t.value = read4b(data, 8); // 残高 t.balance = read4b(data, 12); // 連番 t.id = read3b(data, 1); // 種別 switch (data[0]) { case 0x20: default: t.type = TransType.Debit; // 支払い t.desc = "支払"; t.value = - t.value; // 適用が"支払" だけだと、Money が過去の履歴から店舗名を勝手に // 補完してしまうので、連番を追加しておく。 t.desc += " "; t.desc += t.id.ToString(); break; case 0x02: t.type = TransType.DirectDep; t.desc = "Edyチャージ"; break; case 0x04: t.type = TransType.DirectDep; t.desc = "Edyギフト"; break; } t.memo = ""; return true; }
public override bool analyzeTransaction(Transaction t, byte[] data) { // 日付 int value = read4b(data, 9); int year = (value >> 21) + 2000; int month = (value >> 17) & 0xf; int date = (value >> 12) & 0x1f; int hour = (value >> 6) & 0x3f; int min = value & 0x3f; t.date = new DateTime(year, month, date, hour, min, 0); // 金額 value = read4b(data, 1); // 種別 t.type = TransType.DirectDep; t.value = value; switch (data[0]) { default: case 0x47: t.type = TransType.Debit; // 支払い t.desc = "nanaco支払"; t.value = - value; break; case 0x35: t.desc = "引継"; break; case 0x6f: case 0x70: t.desc = "nanacoチャージ"; break; case 0x83: t.desc = "nanacoポイント交換"; break; } t.memo = ""; // 残高 value = read4b(data, 5); t.balance = value; // 連番 value = read2b(data, 13); t.id = value; return true; }
public void lastBalanceTest() { TestAccount acc = new TestAccount(); acc.isCreditCard = false; accounts.Add(acc); Transaction t; t = new Transaction(); t.date = new DateTime(2010, 4, 1); t.desc = "t1"; t.type = TransType.Payment; t.value = 100; t.balance = 10000; acc.transactions.Add(t); t = new Transaction(); t.date = new DateTime(2010, 4, 1); t.desc = "t2"; t.type = TransType.Payment; t.value = 200; t.balance = 20000; acc.transactions.Add(t); t = new Transaction(); t.date = new DateTime(2010, 1, 1); // 逆順 t.desc = "t3"; t.type = TransType.Payment; t.value = 300; t.balance = 30000; acc.transactions.Add(t); ofx.genOfx(accounts); XmlDocument doc = ofx.doc; // 最も新しい日付の最後の取引(t2)の値になっているかどうか確認 XmlNode ledgerBal = doc.SelectSingleNode("/OFX/BANKMSGSRSV1/STMTTRNRS/STMTRS/LEDGERBAL"); assertNodeText(ledgerBal, "DTASOF", "20100401000000[+9:JST]"); assertNodeText(ledgerBal, "BALAMT", "20000"); }
// Statement Transaction private void statementTransaction(XmlElement parent, Transaction t) { // Statement Transaction XmlElement e = appendElement(parent, "STMTTRN"); appendElementWithText(e, "TRNTYPE", t.GetTransString()); appendElementWithText(e, "DTPOSTED", dateStr(t.date)); appendElementWithText(e, "TRNAMT", t.value.ToString()); // トランザクションの ID は日付と取引番号で生成 appendElementWithText(e, "FITID", t.transId()); appendElementWithText(e, "NAME", limitString(t.desc, 32)); if (t.memo != null) { appendElementWithText(e, "MEMO", t.memo); } }
// 残高 private void ledgerBal(XmlElement parent, Account account, Transaction last) { XmlElement e = appendElement(parent, "LEDGERBAL"); int balance; if (account.hasBalance) { balance = account.balance; } else { balance = last.balance; } appendElementWithText(e, "BALAMT", balance.ToString()); appendElementWithText(e, "DTASOF", dateStr(last.date)); }
private void getFirstLastDate(Account account, out Transaction first, out Transaction last) { first = null; last = null; foreach (Transaction t in account.transactions) { // 先頭エントリ: 同じ日付の場合は、前のエントリを優先 if (first == null || t.date < first.date) { first = t; } // 最終エントリ: 同じ日付の場合は、後のエントリを優先 if (last == null || t.date >= last.date) { last = t; } } }
// 最初のトランザクションと最後のトランザクションを取り出しておく // (日付範囲取得のため) private void getFirstLastDate(List<Account> accounts, out Transaction allFirst, out Transaction allLast) { allFirst = null; allLast = null; foreach (Account account in accounts) { foreach (Transaction t in account.transactions) { // 先頭エントリ: 同じ日付の場合は、前のエントリを優先 if (allFirst == null || t.date < allFirst.date) { allFirst = t; } // 最終エントリ: 同じ日付の場合は、後のエントリを優先 if (allLast == null || t.date >= allLast.date) { allLast = t; } } } }
// Bank Transaction List private void bankTransactionList(XmlElement parent, Account account, Transaction first, Transaction last) { XmlElement e = appendElement(parent, "BANKTRANLIST"); appendElementWithText(e, "DTSTART", dateStr(first.date)); appendElementWithText(e, "DTEND", dateStr(last.date)); foreach (Transaction t in account.transactions.list) { statementTransaction(e, t); } }
public static bool isZeroTransaction(Transaction t) { return t.value == 0; }
// 取引を追加 public void Add(Transaction t) { mList.Add(t); }
//-------------------------------------------------------------------- // 以下のメソッドはサブクラスで必要に応じてオーバライドする /// <summary> /// Transaction 解析 /// </summary> /// <param name="t"></param> /// <param name="data"></param> /// <returns></returns> public abstract bool analyzeTransaction(Transaction t, byte[] data);
/// <summary> /// カード読み込み /// Note: 本来はこのメソッドは private で良いが、UnitTest 用に public にしてある。 /// </summary> /// <param name="f"></param> /// <returns></returns> public TransactionList ReadTransactions(IFelica f) { TransactionList transactions = new TransactionList(); f.Polling(mSystemCode); if (!analyzeCardId(f)) { throw new Exception(Properties.Resources.CantReadCardNo); } for (int i = 0; i < mMaxTransactions; i++) { byte[] data = new byte[16 * mBlocksPerTransaction]; byte[] block = null; for (int j = 0; j < mBlocksPerTransaction; j++) { block = f.ReadWithoutEncryption(mServiceCode, i * mBlocksPerTransaction + j); if (block == null) { break; } block.CopyTo(data, j * 16); } if (block == null) { break; } Transaction t = new Transaction(); // データが全0かどうかチェック int x = 0; foreach (int xx in data) { x |= xx; } if (x == 0) { // データが全0なら無視(空エントリ) t.Invalidate(); } // トランザクション解析 else if (!analyzeTransaction(t, data)) { t.Invalidate(); } transactions.Add(t); } if (mNeedReverse) { transactions.Reverse(); } if (mNeedCalcValue) { CalcValueFromBalance(transactions); } PostProcess(transactions); return transactions; }
// トランザクション解析 public override bool analyzeTransaction(Transaction t, byte[] data) { int ctype = data[0]; // 端末種 int proc = data[1]; // 処理 int date = read2b(data, 4); // 日付 int balance = read2l(data, 10); // 残高(little endian) int seq = read3b(data, 12); // 連番 int region = data[15]; // リージョン // 処理 t.desc = procType(proc); // 残高 t.balance = balance; // 金額は CalcValueFromBalance() で計算する t.value = 0; // 日付 int yy = (date >> 9) + 2000; int mm = (date >> 5) & 0xf; int dd = date & 0x1f; try { t.date = new DateTime(yy, mm, dd, 0, 0, 0); } catch { // 日付異常(おそらく空エントリ) return false; } // ID t.id = seq; // 駅名/店舗名などを調べる int in_line = -1; int in_sta = -1; int out_line, out_sta; StationCode.Names in_name = null, out_name = null; int in_area = 0, out_area = 0; switch (ctype) { case CT_SHOP: case CT_VEND: // 物販/自販機 out_area = Properties.Settings.Default.ShopAreaPriority; //time = (data[6] << 8) | data[7]; out_line = data[8]; out_sta = data[9]; // 優先エリアで検索 out_name = stCode.getShopName(out_area, ctype, out_line, out_sta); if (out_name == null) { // 全エリアで検索 out_name = stCode.getShopName(-1, ctype, out_line, out_sta); } break; case CT_CAR: // 車載端末(バス) out_line = read2b(data, 6); out_sta = read2b(data, 8); out_name = stCode.getBusName(out_line, out_sta); break; default: // それ以外(運賃、チャージなど) in_line = data[6]; in_sta = data[7]; out_line = data[8]; out_sta = data[9]; if (in_line == 0 && in_sta == 0 && out_line == 0 && out_sta == 0) { break; } in_area = getAreaCode(in_line, region); out_area = getAreaCode(out_line, region); in_name = stCode.getStationName(in_area, in_line, in_sta); out_name = stCode.getStationName(out_area, out_line, out_sta); break; } // 備考の先頭には端末種を入れる t.memo = consoleType(ctype); switch (ctype) { case CT_SHOP: case CT_VEND: if (out_name != null) { // "物販" の場合は、"物販" は消して店舗名だけにする if (t.desc == "物販") { t.desc = ""; } else { t.desc += " "; } // 店舗名追加 t.desc += out_name.r1 + " " + out_name.r2; } else { // 店舗名が不明の場合、適用には出線区/出駅順コードをそのまま付与する。 // こうしないと Money が過去の履歴から誤って店舗名を補完してしまい // 都合がわるいため t.desc += " 店舗コード:" + out_line.ToString("X02") + out_sta.ToString("X02"); } break; case CT_CAR: if (out_name != null) { // 適用にバス会社名、備考に停留所名を入れる t.desc += " " + out_name.r1; t.memo += " " + out_name.r2; } break; default: if (in_line == 0 && in_sta == 0 & out_line == 0 && out_sta == 0) { // チャージなどの場合は、何も追加しない break; } // 適用に入会社または出会社を追加 if (in_name != null) { t.desc += " " + in_name.r1; } else if (out_name != null) { t.desc += " " + out_name.r1; } // 備考に入出会社/駅名を記載 t.memo += " "; if (in_name != null) { t.memo += in_name.r1 + "(" + in_name.r2 + ")"; } else { t.memo += string.Format("未登録({0}:{1}:{2})", in_area, in_line, in_sta); } t.memo += " - "; if (out_name != null) { t.memo += out_name.r1 + "(" + out_name.r2 + ")"; } else { t.memo += string.Format("未登録({0}:{1}:{2})", out_area, out_sta, region); } break; } return true; }
private static int compareByDate(Transaction x, Transaction y) { return x.date.CompareTo(y.date); }
// 1行解析 // CSV の各カラムはすでに分解されているものとする public Transaction parse(string[] row) { Transaction t = new Transaction(); // 日付 string date = getCol(row, "Date"); if (date != null) { t.date = CsvUtil.parseDate(date); } else { int year = getColInt(row, "Year"); int month = getColInt(row, "Month"); int day = getColInt(row, "Day"); if (year == 0 || month == 0 || day == 0) { return null; } if (year < 100) { year += 2000; } t.date = new DateTime(year, month, day, 0, 0, 0); } // ID string id = getCol(row, "Id"); if (id != null) { try { t.id = getColInt(row, "Id"); } catch (FormatException) { // just ignore : do not use ID } } // 金額 t.value = getColInt(row, "Income"); t.value -= getColInt(row, "Outgo"); // 残高 t.balance = getColInt(row, "Balance"); // 適用 t.desc = getMultiCol(row, "Desc"); // 備考 t.memo = getMultiCol(row, "Memo"); // トランザクションタイプを自動設定 t.GuessTransType(t.value >= 0); return t; }
public static bool isInvalid(Transaction t) { return !t.mValid; }
/// <summary> /// 取引行の解析 /// </summary> /// <param name="line">取引行</param> /// <returns>成功フラグ</returns> /// public bool readTransaction(string line) { string[] columns = CsvUtil.SplitCsv(line, false); if (columns.Length < 8) { return false; } Transaction transaction = new Transaction(); // 日付の処理 string[] ary = columns[0].Split(new char[] { '/' }); try { if (ary.Length == 3) { transaction.date = new DateTime(int.Parse(ary[0]), int.Parse(ary[1]), int.Parse(ary[2]), 0, 0, 0); } else if (ary.Length == 2) { DateTime now = DateTime.Now; int n1 = int.Parse(ary[0]); int n2 = int.Parse(ary[1]); if (n1 >= 2000) { // 年と月のみ: 日は1日とする transaction.date = new DateTime(n1, n2, 1, 0, 0, 0); } else { // 月と日のみ。年は推定する。 int mm = n1; int dd = n2; DateTime d = new DateTime(now.Year, mm, dd, 0, 0, 0); // 同一年として、日付が6ヶ月以上先の場合、昨年とみなす。 // 逆に6ヶ月以上前の場合、翌年とみなす。 TimeSpan ts = d - now; if (ts.TotalDays > 366 / 2) { d = new DateTime(now.Year - 1, mm, dd, 0, 0, 0); } else if (ts.TotalDays < -366 / 2) { d = new DateTime(now.Year + 1, mm, dd, 0, 0, 0); } transaction.date = d; } } else { return false; } } catch { // 日付が範囲外 (ArgumentRangeOutOfException など) return false; } // 摘要 transaction.desc = columns[1]; // 入金額/出金額 try { transaction.value = int.Parse(columns[2]); } catch { try { transaction.value = -int.Parse(columns[4]); } catch { return false; } } // 残高 try { transaction.balance = int.Parse(columns[6]); } catch { // Note: 残高は入っていない場合もある transaction.balance = 0; } mTransactions.Add(transaction); return true; }
public void setUp() { ofx = new Ofx(); accounts = new List<Account>(); T1 = new Transaction(); T1.date = new DateTime(2000, 1, 1); T1.desc = "T1"; T1.type = TransType.Payment; T1.value = 1000; T1.balance = 10000; T2 = new Transaction(); T2.date = new DateTime(2010, 12, 31); T2.desc = "T2"; T2.type = TransType.Payment; T2.value = 2000; T2.balance = 12000; }