1 /*******************************************************
2 Copyright (C) 2013,2014 Guan Lisheng (guanlisheng@gmail.com)
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 ********************************************************/
18
19 #include "Model_Billsdeposits.h"
20 #include "mmOption.h"
21 #include "Model_Category.h"
22 #include "Model_Account.h"
23 #include "Model_Payee.h"
24
25 const std::vector<std::pair<Model_Billsdeposits::TYPE, wxString> > Model_Billsdeposits::TYPE_CHOICES =
26 {
27 std::make_pair(Model_Billsdeposits::WITHDRAWAL, wxTRANSLATE("Withdrawal"))
28 , std::make_pair(Model_Billsdeposits::DEPOSIT, wxTRANSLATE("Deposit"))
29 , std::make_pair(Model_Billsdeposits::TRANSFER, wxTRANSLATE("Transfer"))
30 };
31
32 const std::vector<std::pair<Model_Billsdeposits::STATUS_ENUM, wxString> > Model_Billsdeposits::STATUS_ENUM_CHOICES =
33 {
34 std::make_pair(Model_Billsdeposits::NONE, wxTRANSLATE("None"))
35 , std::make_pair(Model_Billsdeposits::RECONCILED, wxTRANSLATE("Reconciled"))
36 , std::make_pair(Model_Billsdeposits::VOID_, wxTRANSLATE("Void"))
37 , std::make_pair(Model_Billsdeposits::FOLLOWUP, wxTRANSLATE("Follow up"))
38 , std::make_pair(Model_Billsdeposits::DUPLICATE_, wxTRANSLATE("Duplicate"))
39 };
40
Model_Billsdeposits()41 Model_Billsdeposits::Model_Billsdeposits()
42 : Model<DB_Table_BILLSDEPOSITS_V1>()
43 , m_autoExecuteManual (false)
44 , m_autoExecuteSilent (false)
45 , m_requireExecution (false)
46 , m_allowExecution (false)
47 {
48 }
49
~Model_Billsdeposits()50 Model_Billsdeposits::~Model_Billsdeposits()
51 {
52 }
53
all_type()54 wxArrayString Model_Billsdeposits::all_type()
55 {
56 wxArrayString types;
57 for (const auto& item : TYPE_CHOICES) types.Add(item.second);
58 return types;
59 }
60
all_status()61 wxArrayString Model_Billsdeposits::all_status()
62 {
63 wxArrayString status;
64 for (const auto& item : STATUS_ENUM_CHOICES) status.Add(item.second);
65 return status;
66 }
67
68 /** Return the static instance of Model_Billsdeposits table */
instance()69 Model_Billsdeposits& Model_Billsdeposits::instance()
70 {
71 return Singleton<Model_Billsdeposits>::instance();
72 }
73
74 /**
75 * Initialize the global Model_Billsdeposits table.
76 * Reset the Model_Billsdeposits table or create the table if it does not exist.
77 */
instance(wxSQLite3Database * db)78 Model_Billsdeposits& Model_Billsdeposits::instance(wxSQLite3Database* db)
79 {
80 Model_Billsdeposits& ins = Singleton<Model_Billsdeposits>::instance();
81 ins.db_ = db;
82 ins.destroy_cache();
83 ins.ensure(db);
84
85 return ins;
86 }
87
TRANSDATE(const Data * r)88 wxDate Model_Billsdeposits::TRANSDATE(const Data* r)
89 {
90 return Model::to_date(r->TRANSDATE);
91 }
92
TRANSDATE(const Data & r)93 wxDate Model_Billsdeposits::TRANSDATE(const Data& r)
94 {
95 return Model::to_date(r.TRANSDATE);
96 }
97
NEXTOCCURRENCEDATE(const Data * r)98 wxDate Model_Billsdeposits::NEXTOCCURRENCEDATE(const Data* r)
99 {
100 return Model::to_date(r->NEXTOCCURRENCEDATE);
101 }
102
NEXTOCCURRENCEDATE(const Data & r)103 wxDate Model_Billsdeposits::NEXTOCCURRENCEDATE(const Data& r)
104 {
105 return Model::to_date(r.NEXTOCCURRENCEDATE);
106 }
107
type(const wxString & r)108 Model_Billsdeposits::TYPE Model_Billsdeposits::type(const wxString& r)
109 {
110 static std::map<wxString, TYPE> cache;
111 const auto it = cache.find(r);
112 if (it != cache.end()) return it->second;
113
114 for (const auto& t : TYPE_CHOICES)
115 {
116 if (r.CmpNoCase(t.second) == 0)
117 {
118 cache.insert(std::make_pair(r, t.first));
119 return t.first;
120 }
121 }
122
123 cache.insert(std::make_pair(r, WITHDRAWAL));
124 return WITHDRAWAL;
125 }
type(const Data & r)126 Model_Billsdeposits::TYPE Model_Billsdeposits::type(const Data& r)
127 {
128 return type(r.TRANSCODE);
129 }
type(const Data * r)130 Model_Billsdeposits::TYPE Model_Billsdeposits::type(const Data* r)
131 {
132 return type(r->TRANSCODE);
133 }
status(const wxString & r)134 Model_Billsdeposits::STATUS_ENUM Model_Billsdeposits::status(const wxString& r)
135 {
136 static std::map<wxString, STATUS_ENUM> cache;
137 const auto it = cache.find(r);
138 if (it != cache.end()) return it->second;
139
140 for (const auto & s : STATUS_ENUM_CHOICES)
141 {
142 if (r.CmpNoCase(s.second) == 0)
143 {
144 cache.insert(std::make_pair(r, s.first));
145 return s.first;
146 }
147 }
148
149 STATUS_ENUM ret = NONE;
150 if (r.CmpNoCase("R") == 0) ret = RECONCILED;
151 else if (r.CmpNoCase("V") == 0) ret = VOID_;
152 else if (r.CmpNoCase("F") == 0) ret = FOLLOWUP;
153 else if (r.CmpNoCase("D") == 0) ret = DUPLICATE_;
154 cache.insert(std::make_pair(r, ret));
155
156 return ret;
157 }
status(const Data & r)158 Model_Billsdeposits::STATUS_ENUM Model_Billsdeposits::status(const Data& r)
159 {
160 return status(r.STATUS);
161 }
status(const Data * r)162 Model_Billsdeposits::STATUS_ENUM Model_Billsdeposits::status(const Data* r)
163 {
164 return status(r->STATUS);
165 }
166
toShortStatus(const wxString & fullStatus)167 wxString Model_Billsdeposits::toShortStatus(const wxString& fullStatus)
168 {
169 wxString s = fullStatus.Left(1);
170 s.Replace("N", "");
171 return s;
172 }
173
174 /**
175 * Remove the Data record instance from memory and the database
176 * including any splits associated with the Data Record.
177 */
remove(int id)178 bool Model_Billsdeposits::remove(int id)
179 {
180 for (auto &item : Model_Billsdeposits::splittransaction(get(id)))
181 Model_Budgetsplittransaction::instance().remove(item.SPLITTRANSID);
182 return this->remove(id, db_);
183 }
184
STATUS(STATUS_ENUM status,OP op)185 DB_Table_BILLSDEPOSITS_V1::STATUS Model_Billsdeposits::STATUS(STATUS_ENUM status, OP op)
186 {
187 return DB_Table_BILLSDEPOSITS_V1::STATUS(toShortStatus(all_status()[status]), op);
188 }
189
TRANSCODE(TYPE type,OP op)190 DB_Table_BILLSDEPOSITS_V1::TRANSCODE Model_Billsdeposits::TRANSCODE(TYPE type, OP op)
191 {
192 return DB_Table_BILLSDEPOSITS_V1::TRANSCODE(all_type()[type], op);
193 }
194
splittransaction(const Data * r)195 const Model_Budgetsplittransaction::Data_Set Model_Billsdeposits::splittransaction(const Data* r)
196 {
197 return Model_Budgetsplittransaction::instance().find(Model_Budgetsplittransaction::TRANSID(r->BDID));
198 }
199
splittransaction(const Data & r)200 const Model_Budgetsplittransaction::Data_Set Model_Billsdeposits::splittransaction(const Data& r)
201 {
202 return Model_Budgetsplittransaction::instance().find(Model_Budgetsplittransaction::TRANSID(r.BDID));
203 }
204
decode_fields(const Data & q1)205 void Model_Billsdeposits::decode_fields(const Data& q1)
206 {
207 m_autoExecuteManual = false; // Used when decoding: REPEATS
208 m_autoExecuteSilent = false;
209 m_requireExecution = false;
210 m_allowExecution = false;
211
212 // DeMultiplex the Auto Executable fields from the db entry: REPEATS
213 int repeats = q1.REPEATS;
214 int numRepeats = q1.NUMOCCURRENCES;
215
216 if (repeats >= BD_REPEATS_MULTIPLEX_BASE) // Auto Execute User Acknowlegement required
217 {
218 m_autoExecuteManual = true;
219 repeats -= BD_REPEATS_MULTIPLEX_BASE;
220 }
221
222 if (repeats >= BD_REPEATS_MULTIPLEX_BASE) // Auto Execute Silent mode
223 {
224 m_autoExecuteManual = false; // Can only be manual or auto. Not both
225 m_autoExecuteSilent = true;
226 repeats -= BD_REPEATS_MULTIPLEX_BASE;
227 }
228
229 if ((repeats < Model_Billsdeposits::REPEAT_IN_X_DAYS) || (numRepeats > Model_Billsdeposits::REPEAT_NONE) || (repeats > Model_Billsdeposits::REPEAT_EVERY_X_MONTHS))
230 {
231 m_allowExecution = true;
232 }
233
234 if (this->daysPayment(&q1) < 1)
235 {
236 m_requireExecution = true;
237 }
238 }
239
autoExecuteManual()240 bool Model_Billsdeposits::autoExecuteManual()
241 {
242 return m_autoExecuteManual;
243 }
244
autoExecuteSilent()245 bool Model_Billsdeposits::autoExecuteSilent()
246 {
247 return m_autoExecuteSilent;
248 }
249
requireExecution()250 bool Model_Billsdeposits::requireExecution()
251 {
252 return m_requireExecution;
253 }
254
allowExecution()255 bool Model_Billsdeposits::allowExecution()
256 {
257 return m_allowExecution;
258 }
259
daysPayment(const Data * r)260 int Model_Billsdeposits::daysPayment(const Data* r)
261 {
262 const wxDate& payment_date = Model_Billsdeposits::NEXTOCCURRENCEDATE(r);
263 wxTimeSpan ts = payment_date.Subtract(wxDateTime::Now());
264 int daysRemaining = ts.GetDays();
265 int minutesRemaining = ts.GetMinutes();
266
267 if (minutesRemaining > 0)
268 daysRemaining += 1;
269
270 return daysRemaining;
271 }
272
daysOverdue(const Data * r)273 int Model_Billsdeposits::daysOverdue(const Data* r)
274 {
275 const wxDate& overdue_date = Model_Billsdeposits::TRANSDATE(r);
276 wxTimeSpan ts = overdue_date.Subtract(wxDateTime::Now());
277 int daysRemaining = ts.GetDays();
278 int minutesRemaining = ts.GetMinutes();
279
280 if (minutesRemaining > 0)
281 daysRemaining += 1;
282
283 return daysRemaining;
284 }
285
completeBDInSeries(int bdID)286 void Model_Billsdeposits::completeBDInSeries(int bdID)
287 {
288 Data* bill = get(bdID);
289 if (bill)
290 {
291 int repeats = bill->REPEATS;
292 // DeMultiplex the Auto Executable fields.
293 if (repeats >= BD_REPEATS_MULTIPLEX_BASE) // Auto Execute User Acknowlegement required
294 repeats -= BD_REPEATS_MULTIPLEX_BASE;
295 if (repeats >= BD_REPEATS_MULTIPLEX_BASE) // Auto Execute Silent mode
296 repeats -= BD_REPEATS_MULTIPLEX_BASE;
297 int numRepeats = bill->NUMOCCURRENCES;
298 const wxDateTime& payment_date_current = NEXTOCCURRENCEDATE(bill);
299 const wxDateTime& payment_date_update = nextOccurDate(repeats, numRepeats, payment_date_current);
300
301 const wxDateTime& due_date_current = TRANSDATE(bill);
302 const wxDateTime& due_date_update = nextOccurDate(repeats, numRepeats, due_date_current);
303
304 if (numRepeats != REPEAT_TYPE::REPEAT_INACTIVE)
305 {
306 if ((repeats < REPEAT_TYPE::REPEAT_IN_X_DAYS) || (repeats > REPEAT_TYPE::REPEAT_EVERY_X_MONTHS))
307 numRepeats--;
308 }
309
310 if (repeats == REPEAT_TYPE::REPEAT_NONE)
311 numRepeats = 0;
312 else if ((repeats == REPEAT_TYPE::REPEAT_IN_X_DAYS)
313 || (repeats == REPEAT_TYPE::REPEAT_IN_X_MONTHS))
314 {
315 if (numRepeats != -1) numRepeats = -1;
316 }
317
318 bill->NEXTOCCURRENCEDATE = payment_date_update.FormatISODate();
319 bill->TRANSDATE = due_date_update.FormatISODate();
320
321 // Ensure that TRANDSATE is set correctly
322 if (payment_date_current > due_date_current)
323 bill->TRANSDATE = payment_date_update.FormatISODate();
324
325 bill->NUMOCCURRENCES = numRepeats;
326 save(bill);
327
328 if (bill->NUMOCCURRENCES == REPEAT_TYPE::REPEAT_NONE)
329 remove(bdID);
330 }
331 }
332
nextOccurDate(int repeatsType,int numRepeats,const wxDateTime & nextOccurDate)333 const wxDateTime Model_Billsdeposits::nextOccurDate(int repeatsType, int numRepeats, const wxDateTime& nextOccurDate)
334 {
335 wxDateTime dt = nextOccurDate;
336 if (repeatsType == REPEAT_WEEKLY)
337 dt = dt.Add(wxTimeSpan::Week());
338 else if (repeatsType == REPEAT_BI_WEEKLY)
339 dt = dt.Add(wxTimeSpan::Weeks(2));
340 else if (repeatsType == REPEAT_MONTHLY)
341 dt = dt.Add(wxDateSpan::Month());
342 else if (repeatsType == REPEAT_BI_MONTHLY)
343 dt = dt.Add(wxDateSpan::Months(2));
344 else if (repeatsType == REPEAT_FOUR_MONTHLY)
345 dt = dt.Add(wxDateSpan::Months(4));
346 else if (repeatsType == REPEAT_HALF_YEARLY)
347 dt = dt.Add(wxDateSpan::Months(6));
348 else if (repeatsType == REPEAT_YEARLY)
349 dt = dt.Add(wxDateSpan::Year());
350 else if (repeatsType == REPEAT_QUARTERLY)
351 dt = dt.Add(wxDateSpan::Months(3));
352 else if (repeatsType == REPEAT_FOUR_WEEKLY)
353 dt = dt.Add(wxDateSpan::Weeks(4));
354 else if (repeatsType == REPEAT_DAILY)
355 dt = dt.Add(wxDateSpan::Days(1));
356 else if (repeatsType == REPEAT_IN_X_DAYS) // repeat in numRepeats Days (Once only)
357 dt = dt.Add(wxDateSpan::Days(numRepeats));
358 else if (repeatsType == REPEAT_IN_X_MONTHS) // repeat in numRepeats Months (Once only)
359 dt = dt.Add(wxDateSpan::Months(numRepeats));
360 else if (repeatsType == REPEAT_EVERY_X_DAYS) // repeat every numRepeats Days
361 dt = dt.Add(wxDateSpan::Days(numRepeats));
362 else if (repeatsType == REPEAT_EVERY_X_MONTHS) // repeat every numRepeats Months
363 dt = dt.Add(wxDateSpan::Months(numRepeats));
364 else if (repeatsType == REPEAT_MONTHLY_LAST_DAY
365 || REPEAT_MONTHLY_LAST_BUSINESS_DAY == repeatsType)
366 {
367 dt = dt.Add(wxDateSpan::Month());
368 dt = dt.SetToLastMonthDay(dt.GetMonth(), dt.GetYear());
369 if (repeatsType == REPEAT_MONTHLY_LAST_BUSINESS_DAY) // last weekday of month
370 {
371 if (dt.GetWeekDay() == wxDateTime::Sun || dt.GetWeekDay() == wxDateTime::Sat)
372 dt.SetToPrevWeekDay(wxDateTime::Fri);
373 }
374 }
375 wxLogDebug("init date: %s -> next date: %s", nextOccurDate.FormatISODate(), dt.FormatISODate());
376 return dt;
377 }
378
Full_Data()379 Model_Billsdeposits::Full_Data::Full_Data()
380 {}
381
Full_Data(const Data & r)382 Model_Billsdeposits::Full_Data::Full_Data(const Data& r) : Data(r)
383 {
384 const auto& bill_split = splittransaction(r);
385 if (!bill_split.empty())
386 {
387 for (const auto& entry : bill_split)
388 CATEGNAME += (CATEGNAME.empty() ? " * " : ", ")
389 + Model_Category::full_name(entry.CATEGID, entry.SUBCATEGID);
390 }
391 else
392 CATEGNAME = Model_Category::full_name(r.CATEGID, r.SUBCATEGID);
393
394 ACCOUNTNAME = Model_Account::get_account_name(r.ACCOUNTID);
395
396 PAYEENAME = Model_Payee::get_payee_name(r.PAYEEID);
397 if (Model_Billsdeposits::type(r) == Model_Billsdeposits::TRANSFER)
398 {
399 PAYEENAME = Model_Account::get_account_name(r.TOACCOUNTID);
400 }
401 }
402