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