Source code: com/pjsofts/eurobudget/format/qif/QIFNonInvestmentItem.java
1 /*
2 * QIFNonInvestmentItem.java
3 *
4 */
5
6 package com.pjsofts.eurobudget.format.qif;
7
8 import com.pjsofts.eurobudget.DataModel;
9 import com.pjsofts.eurobudget.EBConstants;
10 import com.pjsofts.eurobudget.beans.*;
11 import java.io.IOException;
12 import java.text.ParseException;
13 import java.util.Date;
14 import java.util.ResourceBundle;
15
16
17 /** datas of group in QIF already a bit decoded without prefix letter
18 <PRE>
19 Items for Non-Investment Accounts
20
21 Each item in a bank, cash, credit card, other liability, or other asset account must begin with a letter that indicates the field in the Quicken register. The non-split items can be in any sequence:
22
23 Field Indicator Explanation
24 D Date
25 T Amount
26 C Cleared status
27 N Num (check or reference number)
28 P Payee
29 M Memo
30 A Address (up to five lines; the sixth line is an optional message)
31 L Category (Category/Subcategory/Transfer/Class)
32 S Category in split (Category/Transfer/Class)
33 E Memo in split
34 $ Dollar amount of split
35 ^ End of the entry
36
37 Note: Repeat the S, E, and $ lines as many times as needed for additional items in a split. If an item is omitted from the transaction in the QIF file, Quicken treats it as a blank item.
38 </PRE>
39 */
40 public class QIFNonInvestmentItem {
41 private static final ResourceBundle i18n = EBConstants.i18n;
42 // corresponding Data decoded :
43 /** date */
44 public Date d = null;
45 /** cleared status */
46 public String c = null;
47 /** Memo */
48 public String m = null;
49 /** Memo in split */
50 public String e = null;
51 /** amount */
52 public Double t = null;
53 /** num (check or ref number as String ) */
54 public Long n = null;
55 /** Address (up to five lines; the sixth line is an optional message) */
56 public String a = null;
57 /** Payee */
58 public String p = null;
59 /** Category (Category/Subcategory/Transfer/Class) */
60 public String l = null;//without []
61 /** is complex category */
62 boolean tl_with_brackets = false;
63 /** Dollar amount of split */
64 public String dollardsign = null;
65
66 public boolean isNull() {
67 return d == null && c == null && m == null && t == null
68 && n == null && p == null && l == null && a == null;
69 }
70
71 /** export one transaction */
72 public static String toQIF(Transaction t) {
73 StringBuffer sb = new StringBuffer(50);
74 // DATE
75 sb.append("D");sb.append(QIF.DF.format(t.getDate()));
76 sb.append("\n");
77 // C TYPE
78 if ( t.isVerified() ) {
79 sb.append("CX\n");
80 } else if ( t.isChecked() ) {
81 sb.append("C*\n");
82 } else {
83 //nothing
84 }
85 // M
86 if ( t.getDetails() != null ) {
87 sb.append("M");
88 sb.append(t.getDetails());//t.getLibelle());
89 sb.append("\n");
90 }
91 // AMOUNT
92 sb.append("T");
93 sb.append(QIF.NF.format(t.getRealAmount()));
94 sb.append("\n");
95 // N, Check number
96 if ( t instanceof Check) {
97 Check c = (Check)t;
98 sb.append("N");
99 sb.append(c.getNumber().toString());
100 sb.append("\n");
101 }
102 // P
103 if ( t.getEntityName() != null && t.getEntityName().length() != 0 ) {
104 sb.append("P");
105 sb.append(t.getEntityName());
106 sb.append("\n");
107 }
108 // L Category
109 if ( t instanceof Virement) {
110 Virement v = (Virement)t;
111 sb.append("L");
112 sb.append("["+v.getTargetAccount().getName()+"]");
113 sb.append("\n");
114 // RelatedVirement need specific code ?? no
115 } else {
116 if ( t.getCategoryPair() != null && !t.getCategoryPair().isNull()
117 && t.getCategoryPair().getMain() != null
118 && t.getCategoryPair().getMain() != Category.CATEGORY_NULL ) {
119 sb.append("L");
120 sb.append(t.getCategoryPair().getMain().getName());
121 if ( t.getCategoryPair().getSub() != null
122 && t.getCategoryPair().getSub() != Category.CATEGORY_NULL ) {
123 sb.append(":");
124 sb.append(t.getCategoryPair().getSub().getName());
125 }
126 sb.append("\n");
127 }
128 }
129 // end of group
130 sb.append("^\n");
131 return sb.toString();
132 }
133
134 /**
135 * @param currentLine first line of the block of line
136 * @param break following lines
137 * @param checkRuleDT true means check rules:D,T Mandatory
138 *
139 * @return qif lines in their respective variables or null if all nulls
140 */
141 public static QIFNonInvestmentItem readItem(String currentLine, CacheReader br, boolean checkRuleDT) throws IOException, ParseException {
142 //Normal order = D,C,M,T,P,L
143 String line;
144 QIFNonInvestmentItem datas = new QIFNonInvestmentItem();
145 //
146 line = currentLine;
147 while ( line != null && !line.startsWith("^") ) {
148 if ( line.startsWith("D") ) {
149 if ( datas.d != null ) {
150 throw new ParseException(i18n.getString("QIF_error_D_duplicate"),br.getLineNumber());
151 } else {
152 try {
153 line = line.trim().substring(1);
154 line = line.replace('\'','/');//20yy, 19yy
155 datas.d = QIF.DF.parse(line);
156 } catch (ParseException pe){
157 throw new ParseException(line,br.getLineNumber());
158 }
159 }
160 } else if ( line.startsWith("C") ) {
161 line = line.trim().substring(1);
162 if ( datas.c != null ) {
163 throw new ParseException(i18n.getString("QIF_error_C_duplicate"),br.getLineNumber());
164 } else {
165 if ( !line.startsWith("*") && !line.startsWith("X") ) {
166 // should not happend this should raise one exception during parsing only
167 throw new ParseException(i18n.getString("QIF_error_C_decoding")+ " :"+line, br.getLineNumber());
168 }
169 datas.c = line;
170 }
171 } else if ( line.startsWith("M") ) {
172 line = line.trim().substring(1);
173 if ( datas.m != null ) {
174 //throw new java.text.ParseException(i18n.getString("QIF_error_M_duplicate"),br.getLineNumber());
175 datas.m = datas.m + " " +line;
176 } else {
177 datas.m = line;
178 }
179 } else if ( line.startsWith("N") ) {
180 line = line.trim().substring(1);
181 if ( datas.n != null ) {
182 throw new ParseException(i18n.getString("QIF_error_N_duplicate"),br.getLineNumber());
183 } else {
184 try {
185 datas.n = new Long(QIF.NF.parse(line).longValue());
186 } catch (ParseException pe){
187 // maybe a reference string that we discard ...
188 if ( datas.m != null ) {
189 datas.m = datas.m + " " +line;
190 } else {
191 datas.m = line;
192 }
193 }
194 }
195 } else if ( line.startsWith("T") ) {
196 line = line.trim().substring(1);
197 if ( datas.t != null ) {
198 throw new ParseException(i18n.getString("QIF_error_T_duplicate"),br.getLineNumber());
199 } else {
200 try {
201 datas.t = new Double(QIF.NF.parse(line).doubleValue());
202 } catch (ParseException pe){
203 throw new ParseException(line,br.getLineNumber());
204 }
205 }
206 } else if ( line.startsWith("P") ) {
207 line = line.trim().substring(1);
208 if ( datas.p != null ) {
209 throw new ParseException(i18n.getString("QIF_error_P_duplicate"),br.getLineNumber());
210 } else {
211 datas.p = line;
212 }
213 } else if ( line.startsWith("A") ) {
214 line = line.trim().substring(1);
215 if ( datas.a != null ) {
216 datas.a = datas.a + " "+ line;
217 } else {
218 datas.a = line;
219 }
220 } else if ( line.startsWith("E") ) {
221 line = line.trim().substring(1);
222 if ( datas.e != null ) {
223 datas.e = datas.e + " "+ line;
224 } else {
225 datas.e = line;
226 }
227 } else if ( line.startsWith("$") ) {
228 line= line.trim().substring(1);
229 if ( datas.dollardsign != null ) {
230 datas.dollardsign = datas.dollardsign + " "+ line;
231 } else {
232 datas.dollardsign = line;
233 }
234 } else if ( line.startsWith("L") ) {
235 line = line.trim().substring(1);
236 if ( datas.l != null ) {
237 throw new ParseException(i18n.getString("QIF_error_L_duplicate"),br.getLineNumber());
238 } else {
239 if ( line.startsWith("[") && line.endsWith("]") ) {
240 datas.l = line.substring(1,line.length()-1);
241 datas.tl_with_brackets = true;
242 } else {
243 datas.l = line;
244 datas.tl_with_brackets = false;
245 }
246 }
247 } else if ( line.length() == 0 ) {
248 // do nothing
249 } else throw new ParseException(i18n.getString("QIF_wrong_prefix")+line,br.getLineNumber());
250 line = br.readLine();
251 }
252 // Mandatory = D,T
253 if ( checkRuleDT && !datas.isNull() && (datas.d == null || datas.t == null ) ) {
254 throw new ParseException(i18n.getString("QIF_DT_mandatory"),br.getLineNumber());
255 }
256 if ( datas.isNull() )
257 return null;
258 return datas;
259 }
260
261 /**
262 * Do any business logic rules to recreate a good transaction
263 * In charge of adding it to the right account(currentAccount most of the time, except for some virement).
264 * some params may be null
265 * precondition, c,d,t are not null
266 *
267 * @param dm DataModel used to created not existing entity, category ...
268 */
269 public static Transaction toEBTransaction(DataModel dm, Account currentAccount, QIFNonInvestmentItem datas) {
270 Transaction t = null;
271 boolean negativeAmount = datas.t.doubleValue() < 0.0d;
272 if ( datas.n != null ) {
273 // Check?
274 Check c = new Check();
275 t=c;
276 c.setNumber(datas.n);
277 } else if ( datas.l != null && datas.tl_with_brackets ) {
278 // RelatedVirement?
279 // Virement?
280 Virement v = new Virement();
281 t=v;
282 Account targetAccount = dm.getAccountOrCreate(datas.l);
283 if ( negativeAmount ) {
284 v.setSourceAccount(currentAccount);
285 v.setTargetAccount(targetAccount);
286 } else {
287 v.setSourceAccount(targetAccount);
288 v.setTargetAccount(currentAccount);
289 }
290 } else if ( negativeAmount ) {
291 // Payment ?
292 t = new Payment();
293 } else {
294 // Versement ?
295 t = new Versement();
296 }
297 // C
298 if ( datas.c != null ) {
299 if ( datas.c.startsWith("*") ) {
300 t.setChecked(true);
301 } else if ( datas.c.startsWith("X") ) {
302 t.setChecked(true);
303 t.setVerified(true);
304 } else {
305 // should not happend this should raise one exception during parsing only
306 }
307 }
308 // D
309 t.setDate(datas.d);
310 // M
311 if ( datas.m != null )
312 t.setDetails(datas.m);
313 // T
314 t.setAmount(Math.abs(datas.t.doubleValue()));
315 // P/ Entity
316 if ( datas.p != null ) {
317 //should we create the entity if not Payment or Versement ??
318 Entity entity = dm.getEntityFromString(datas.p);
319 if ( t instanceof Payment ) {
320 Payment payment = (Payment)t;
321 payment.setToEntity(entity);
322 } else if (t instanceof Versement) {
323 Versement v = (Versement)t;
324 v.setFromEntity(entity);
325 } else {
326 //this may happen, Money allows it
327 //throw new java.text.ParseException("P/This entity is not in a Versement or Payment",br.getLineNumber());
328 t.setDetails(t.getDetails()+" "+entity.getName());
329 }
330 }
331 // L / Category , not Bank Account !
332 if ( datas.l != null && !(t instanceof Virement) ) {
333 String cat1,cat2 = null;
334 int charind = datas.l.indexOf(":");
335 if ( charind < 0 ) {
336 cat1 = datas.l; cat2 = null;
337 } else {
338 cat1 = datas.l.substring(0,charind);
339 cat2 = datas.l.substring(charind+1,datas.l.length());
340 }
341 int type = (t instanceof Payment) ? CategoryPair.TYPE_COST : CategoryPair.TYPE_REVENUE;
342 Category category = dm.getCategoryFromString(cat1,type);
343 CategoryPair ct = new CategoryPair();
344 ct.setMain(category);
345 if ( cat2 != null) {
346 Category subCategory = dm.getSubCategoryFromString(category, cat2,type);
347 ct.setSub(subCategory);
348 }
349 t.setCategoryPair(ct);
350 }
351
352 return t;
353 }
354
355 }