Merge lp:~openbig/bigconsulting/scheduler into lp:bigconsulting
- scheduler
- Merge into addons
Proposed by
gpa(OpenERP)
Status: | Merged |
---|---|
Merged at revision: | 89 |
Proposed branch: | lp:~openbig/bigconsulting/scheduler |
Merge into: | lp:bigconsulting |
Diff against target: |
343 lines (+251/-3) 6 files modified
account_invoice_cash_discount/account_invoice_cash_discount.py (+202/-0) account_invoice_cash_discount/wizard/wizard_discount_reconcile.py (+8/-2) account_payment_discount_extension/__terp__.py (+1/-0) account_payment_discount_extension/account_payment_discount.py (+22/-1) account_payment_discount_extension/account_payment_discount_data.xml (+17/-0) account_payment_discount_extension/wizard/wizard_payment_discount_order.py (+1/-0) |
To merge this branch: | bzr merge lp:~openbig/bigconsulting/scheduler |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
openbig | Pending | ||
Review via email: mp+35365@code.launchpad.net |
Commit message
Description of the change
added scheduler
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'account_invoice_cash_discount/account_invoice_cash_discount.py' | |||
2 | --- account_invoice_cash_discount/account_invoice_cash_discount.py 2010-08-13 10:34:49 +0000 | |||
3 | +++ account_invoice_cash_discount/account_invoice_cash_discount.py 2010-09-14 05:41:08 +0000 | |||
4 | @@ -24,6 +24,8 @@ | |||
5 | 24 | from tools import config | 24 | from tools import config |
6 | 25 | import time | 25 | import time |
7 | 26 | from tools.translate import _ | 26 | from tools.translate import _ |
8 | 27 | import netsvc | ||
9 | 28 | |||
10 | 27 | 29 | ||
11 | 28 | class account_payment_term(osv.osv): | 30 | class account_payment_term(osv.osv): |
12 | 29 | _name = "account.payment.term" | 31 | _name = "account.payment.term" |
13 | @@ -871,5 +873,205 @@ | |||
14 | 871 | } | 873 | } |
15 | 872 | account_bank_statement_line() | 874 | account_bank_statement_line() |
16 | 873 | 875 | ||
17 | 876 | |||
18 | 877 | class account_move_line(osv.osv): | ||
19 | 878 | _inherit="account.move.line" | ||
20 | 879 | |||
21 | 880 | def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}): | ||
22 | 881 | |||
23 | 882 | id_set = ','.join(map(str, ids)) | ||
24 | 883 | res_users_obj = self.pool.get('res.users') | ||
25 | 884 | lines = self.browse(cr, uid, ids, context=context) | ||
26 | 885 | unrec_lines = filter(lambda x: not x['reconcile_id'], lines) | ||
27 | 886 | credit = debit = 0.0 | ||
28 | 887 | currency = 0.0 | ||
29 | 888 | account_id = False | ||
30 | 889 | partner_id = False | ||
31 | 890 | invoice = False | ||
32 | 891 | writeoff_lines = [] | ||
33 | 892 | for line in unrec_lines: | ||
34 | 893 | if line.invoice: | ||
35 | 894 | invoice = line.invoice | ||
36 | 895 | if line.state <> 'valid': | ||
37 | 896 | raise osv.except_osv(_('Error'), | ||
38 | 897 | _('Entry "%s" is not valid !') % line.name) | ||
39 | 898 | credit += line['credit'] | ||
40 | 899 | debit += line['debit'] | ||
41 | 900 | currency += line['amount_currency'] or 0.0 | ||
42 | 901 | account_id = line['account_id']['id'] | ||
43 | 902 | account_type = line['account_id']['type'] | ||
44 | 903 | partner_id = (line['partner_id'] and line['partner_id']['id']) or False | ||
45 | 904 | writeoff = debit - credit | ||
46 | 905 | # Ifdate_p in context => take this date | ||
47 | 906 | if context.has_key('date_p') and context['date_p']: | ||
48 | 907 | date=context['date_p'] | ||
49 | 908 | else: | ||
50 | 909 | date = time.strftime('%Y-%m-%d') | ||
51 | 910 | |||
52 | 911 | cr.execute('SELECT account_id, reconcile_id '\ | ||
53 | 912 | 'FROM account_move_line '\ | ||
54 | 913 | 'WHERE id IN %s '\ | ||
55 | 914 | 'GROUP BY account_id,reconcile_id', | ||
56 | 915 | (tuple(ids),)) | ||
57 | 916 | r = cr.fetchall() | ||
58 | 917 | #TODO: move this check to a constraint in the account_move_reconcile object | ||
59 | 918 | if (len(r) != 1) and not context.get('fy_closing', False): | ||
60 | 919 | raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! ')) | ||
61 | 920 | if not unrec_lines: | ||
62 | 921 | raise osv.except_osv(_('Error'), _('Entry is already reconciled')) | ||
63 | 922 | account = self.pool.get('account.account').browse(cr, uid, account_id, context=context) | ||
64 | 923 | if not context.get('fy_closing', False) and not account.reconcile: | ||
65 | 924 | raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !')) | ||
66 | 925 | if r[0][1] != None: | ||
67 | 926 | raise osv.except_osv(_('Error'), _('Some entries are already reconciled !')) | ||
68 | 927 | |||
69 | 928 | if (not self.pool.get('res.currency').is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \ | ||
70 | 929 | (account.currency_id and (not self.pool.get('res.currency').is_zero(cr, uid, account.currency_id, currency))): | ||
71 | 930 | if not writeoff_acc_id: | ||
72 | 931 | raise osv.except_osv(_('Warning'), _('You have to provide an account for the write off entry !')) | ||
73 | 932 | if writeoff > 0: | ||
74 | 933 | debit = writeoff | ||
75 | 934 | credit = 0.0 | ||
76 | 935 | self_credit = writeoff | ||
77 | 936 | self_debit = 0.0 | ||
78 | 937 | else: | ||
79 | 938 | debit = 0.0 | ||
80 | 939 | credit = -writeoff | ||
81 | 940 | self_credit = 0.0 | ||
82 | 941 | self_debit = -writeoff | ||
83 | 942 | |||
84 | 943 | # If comment exist in context, take it | ||
85 | 944 | if 'comment' in context and context['comment']: | ||
86 | 945 | libelle=context['comment'] | ||
87 | 946 | else: | ||
88 | 947 | libelle='Write-Off' | ||
89 | 948 | |||
90 | 949 | cur_obj = self.pool.get('res.currency') | ||
91 | 950 | cur_id = False | ||
92 | 951 | amount_cur = 0.00 | ||
93 | 952 | |||
94 | 953 | writeoff_lines.append( | ||
95 | 954 | (0, 0, { | ||
96 | 955 | 'name':libelle, | ||
97 | 956 | 'debit':self_debit, | ||
98 | 957 | 'credit':self_credit, | ||
99 | 958 | 'account_id':account_id, | ||
100 | 959 | 'date':date, | ||
101 | 960 | 'partner_id':partner_id, | ||
102 | 961 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
103 | 962 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
104 | 963 | })), | ||
105 | 964 | |||
106 | 965 | writeoff_lines.append((0, 0, { | ||
107 | 966 | 'name':libelle, | ||
108 | 967 | 'debit':debit, | ||
109 | 968 | 'credit':credit, | ||
110 | 969 | 'account_id':writeoff_acc_id, | ||
111 | 970 | 'analytic_account_id': context.get('analytic_id', False), | ||
112 | 971 | 'date':date, | ||
113 | 972 | 'partner_id':partner_id, | ||
114 | 973 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
115 | 974 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
116 | 975 | })) | ||
117 | 976 | |||
118 | 977 | ##################################################### | ||
119 | 978 | if invoice.type == 'out_invoice': | ||
120 | 979 | partner_acc_id = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context).property_account_receivable.id | ||
121 | 980 | elif invoice.type=='in_invoice': | ||
122 | 981 | partner_acc_id = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context).property_account_payable.id | ||
123 | 982 | |||
124 | 983 | if context.get('company_currency_id',False) != context.get('currency_id',False): | ||
125 | 984 | expense_acc = res_users_obj.browse(cr, uid, uid, context=context).company_id.expense_account_id | ||
126 | 985 | revene_acc = res_users_obj.browse(cr, uid, uid, context=context).company_id.revenue_account_id | ||
127 | 986 | |||
128 | 987 | amount_cur = cur_obj.compute(cr, uid, context.get('company_currency_id',False), context.get('currency_id',False), abs(writeoff), context=context) | ||
129 | 988 | cur_id = context.get('currency_id',False) | ||
130 | 989 | currency_diff = amount_cur - abs(writeoff) | ||
131 | 990 | |||
132 | 991 | if currency_diff > 0.0: | ||
133 | 992 | writeoff_lines.append((0, 0, { | ||
134 | 993 | 'name':libelle, | ||
135 | 994 | 'debit':0.0, | ||
136 | 995 | 'credit':abs(currency_diff), | ||
137 | 996 | 'account_id':revene_acc.id, | ||
138 | 997 | 'date':date, | ||
139 | 998 | 'partner_id':partner_id, | ||
140 | 999 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
141 | 1000 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
142 | 1001 | })), | ||
143 | 1002 | |||
144 | 1003 | writeoff_lines.append((0, 0, { | ||
145 | 1004 | 'name':libelle, | ||
146 | 1005 | 'debit':abs(currency_diff), | ||
147 | 1006 | 'credit':0.0, | ||
148 | 1007 | 'account_id':2, | ||
149 | 1008 | 'analytic_account_id': context.get('analytic_id', False), | ||
150 | 1009 | 'date':date, | ||
151 | 1010 | 'partner_id':partner_id, | ||
152 | 1011 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
153 | 1012 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
154 | 1013 | })) | ||
155 | 1014 | else: | ||
156 | 1015 | writeoff_lines.append((0, 0, { | ||
157 | 1016 | 'name':libelle, | ||
158 | 1017 | 'debit':abs(currency_diff), | ||
159 | 1018 | 'credit':0.0, | ||
160 | 1019 | 'account_id':3, | ||
161 | 1020 | 'date':date, | ||
162 | 1021 | 'partner_id':partner_id, | ||
163 | 1022 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
164 | 1023 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
165 | 1024 | })), | ||
166 | 1025 | |||
167 | 1026 | writeoff_lines.append((0, 0, { | ||
168 | 1027 | 'name':libelle, | ||
169 | 1028 | 'debit':0.0, | ||
170 | 1029 | 'credit':abs(currency_diff), | ||
171 | 1030 | 'account_id':expense_acc.id, | ||
172 | 1031 | 'analytic_account_id': context.get('analytic_id', False), | ||
173 | 1032 | 'date':date, | ||
174 | 1033 | 'partner_id':partner_id, | ||
175 | 1034 | 'currency_id': cur_id or (account.currency_id.id or False), | ||
176 | 1035 | 'amount_currency': amount_cur or (account.currency_id.id and -currency or 0.0) | ||
177 | 1036 | })) | ||
178 | 1037 | ##########################################3 | ||
179 | 1038 | |||
180 | 1039 | writeoff_move_id = self.pool.get('account.move').create(cr, uid, { | ||
181 | 1040 | 'period_id': writeoff_period_id, | ||
182 | 1041 | 'journal_id': writeoff_journal_id, | ||
183 | 1042 | 'date':date, | ||
184 | 1043 | 'state': 'draft', | ||
185 | 1044 | 'line_id': writeoff_lines | ||
186 | 1045 | }) | ||
187 | 1046 | writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)]) | ||
188 | 1047 | if account_id == writeoff_acc_id: | ||
189 | 1048 | writeoff_line_ids = [writeoff_line_ids[1]] | ||
190 | 1049 | |||
191 | 1050 | if invoice: | ||
192 | 1051 | if (invoice.type == 'out_invoice') or (invoice.type == 'in_invoice'): | ||
193 | 1052 | condition_1 = (account_type == 'payable' and writeoff > 0.0) | ||
194 | 1053 | condition_2 = (account_type == 'receivable' and writeoff < 0.0) | ||
195 | 1054 | else: | ||
196 | 1055 | condition_1 = (account_type == 'payable' and writeoff < 0.0) | ||
197 | 1056 | condition_2 = (account_type == 'receivable' and writeoff > 0.0) | ||
198 | 1057 | if condition_1 or condition_2: | ||
199 | 1058 | writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', 'in', (account_id,writeoff_acc_id))]) | ||
200 | 1059 | self.pool.get('account.move.line').write(cr ,uid, writeoff_line_ids, {'name':_('Currency Profit/Loss')}) | ||
201 | 1060 | ids += writeoff_line_ids | ||
202 | 1061 | |||
203 | 1062 | r_id = self.pool.get('account.move.reconcile').create(cr, uid, { | ||
204 | 1063 | #'name': date, | ||
205 | 1064 | 'type': type, | ||
206 | 1065 | 'line_id': map(lambda x: (4,x,False), ids), | ||
207 | 1066 | 'line_partial_ids': map(lambda x: (3,x,False), ids) | ||
208 | 1067 | }) | ||
209 | 1068 | wf_service = netsvc.LocalService("workflow") | ||
210 | 1069 | # the id of the move.reconcile is written in the move.line (self) by the create method above | ||
211 | 1070 | # because of the way the line_id are defined: (4, x, False) | ||
212 | 1071 | for id in ids: | ||
213 | 1072 | wf_service.trg_trigger(uid, 'account.move.line', id, cr) | ||
214 | 1073 | return r_id | ||
215 | 1074 | |||
216 | 1075 | account_move_line() | ||
217 | 874 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 1076 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
218 | 875 | 1077 | ||
219 | 876 | 1078 | ||
220 | === modified file 'account_invoice_cash_discount/wizard/wizard_discount_reconcile.py' | |||
221 | --- account_invoice_cash_discount/wizard/wizard_discount_reconcile.py 2010-09-13 09:11:53 +0000 | |||
222 | +++ account_invoice_cash_discount/wizard/wizard_discount_reconcile.py 2010-09-14 05:41:08 +0000 | |||
223 | @@ -69,9 +69,13 @@ | |||
224 | 69 | def _trans_rec_reconcile(self, cr, uid, data, context=None): | 69 | def _trans_rec_reconcile(self, cr, uid, data, context=None): |
225 | 70 | pool = pooler.get_pool(cr.dbname) | 70 | pool = pooler.get_pool(cr.dbname) |
226 | 71 | account_move_line_obj = pool.get('account.move.line') | 71 | account_move_line_obj = pool.get('account.move.line') |
228 | 72 | 72 | res_users_obj = pool.get('res.users') | |
229 | 73 | invoice_obj = pool.get('account.inovice') | ||
230 | 74 | |||
231 | 73 | form = data['form'] | 75 | form = data['form'] |
232 | 74 | account_id = form.get('writeoff_acc_id', False) or form.get('discount_acc_id', False) | 76 | account_id = form.get('writeoff_acc_id', False) or form.get('discount_acc_id', False) |
233 | 77 | company_currecy_id = res_users_obj.browse(cr, uid, uid, context=context).company_id.currency_id.id | ||
234 | 78 | invoice_currecy_id = account_move_line_obj.browse(cr, uid, data['id'], context=context).invoice.currency_id.id | ||
235 | 75 | context['date_p'] = form.get('date_p', False) | 79 | context['date_p'] = form.get('date_p', False) |
236 | 76 | date = False | 80 | date = False |
237 | 77 | if context['date_p']: | 81 | if context['date_p']: |
238 | @@ -80,7 +84,9 @@ | |||
239 | 80 | period_id = False | 84 | period_id = False |
240 | 81 | if len(ids): | 85 | if len(ids): |
241 | 82 | period_id = ids[0] | 86 | period_id = ids[0] |
243 | 83 | 87 | ||
244 | 88 | context['company_currency_id'] = company_currecy_id | ||
245 | 89 | context['currency_id'] = invoice_currecy_id | ||
246 | 84 | journal_id = form.get('journal_id', False) | 90 | journal_id = form.get('journal_id', False) |
247 | 85 | context['comment'] = form.get('comment', False) | 91 | context['comment'] = form.get('comment', False) |
248 | 86 | context['analytic_id'] = form.get('analytic_id', False) | 92 | context['analytic_id'] = form.get('analytic_id', False) |
249 | 87 | 93 | ||
250 | === modified file 'account_payment_discount_extension/__terp__.py' | |||
251 | --- account_payment_discount_extension/__terp__.py 2010-08-20 12:17:29 +0000 | |||
252 | +++ account_payment_discount_extension/__terp__.py 2010-09-14 05:41:08 +0000 | |||
253 | @@ -35,6 +35,7 @@ | |||
254 | 35 | "update_xml" : [ | 35 | "update_xml" : [ |
255 | 36 | "account_payment_disocunt_wizard.xml", | 36 | "account_payment_disocunt_wizard.xml", |
256 | 37 | "account_payment_discount_view.xml", | 37 | "account_payment_discount_view.xml", |
257 | 38 | "account_payment_discount_data.xml", | ||
258 | 38 | ], | 39 | ], |
259 | 39 | "active": False, | 40 | "active": False, |
260 | 40 | "installable": True, | 41 | "installable": True, |
261 | 41 | 42 | ||
262 | === modified file 'account_payment_discount_extension/account_payment_discount.py' | |||
263 | --- account_payment_discount_extension/account_payment_discount.py 2010-09-20 14:25:34 +0000 | |||
264 | +++ account_payment_discount_extension/account_payment_discount.py 2010-09-14 05:41:08 +0000 | |||
265 | @@ -25,7 +25,7 @@ | |||
266 | 25 | import time | 25 | import time |
267 | 26 | from datetime import datetime | 26 | from datetime import datetime |
268 | 27 | from dateutil.relativedelta import relativedelta | 27 | from dateutil.relativedelta import relativedelta |
270 | 28 | 28 | from mx import DateTime | |
271 | 29 | 29 | ||
272 | 30 | class payment_order(osv.osv): | 30 | class payment_order(osv.osv): |
273 | 31 | _inherit='payment.order' | 31 | _inherit='payment.order' |
274 | @@ -39,6 +39,7 @@ | |||
275 | 39 | _columns={ | 39 | _columns={ |
276 | 40 | 'next_payment_date': fields.date('Next Payment Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},), | 40 | 'next_payment_date': fields.date('Next Payment Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},), |
277 | 41 | } | 41 | } |
278 | 42 | |||
279 | 42 | def action_date_assign(self, cr, uid, ids, *args): | 43 | def action_date_assign(self, cr, uid, ids, *args): |
280 | 43 | data = super(account_invoice,self).action_date_assign(cr, uid, ids, *args) | 44 | data = super(account_invoice,self).action_date_assign(cr, uid, ids, *args) |
281 | 44 | for inv in self.browse(cr, uid, ids): | 45 | for inv in self.browse(cr, uid, ids): |
282 | @@ -48,7 +49,27 @@ | |||
283 | 48 | delay = payment_data[0].delay | 49 | delay = payment_data[0].delay |
284 | 49 | self.write(cr, uid, [inv.id], {'next_payment_date':(datetime.now() + relativedelta(days=delay)).strftime('%Y-%m-%d')}) | 50 | self.write(cr, uid, [inv.id], {'next_payment_date':(datetime.now() + relativedelta(days=delay)).strftime('%Y-%m-%d')}) |
285 | 50 | return data | 51 | return data |
286 | 52 | |||
287 | 53 | def _next_date(self, cr, uid, ids, context=None): | ||
288 | 54 | payment_term_obj = self.pool.get('account.payment.term') | ||
289 | 55 | current_date = datetime.now().strftime('%Y-%m-%d') | ||
290 | 56 | ids = self.search(cr, uid, [('state','=','open')]) | ||
291 | 57 | for id in ids: | ||
292 | 58 | invoice_data = self.browse(cr, uid, id, context=context) | ||
293 | 59 | next_pay_date = invoice_data.next_payment_date | ||
294 | 60 | payment_data = payment_term_obj.browse(cr, uid, invoice_data.payment_term.id) | ||
295 | 61 | if next_pay_date: | ||
296 | 62 | if next_pay_date < current_date: | ||
297 | 63 | for dis_line in payment_data.cash_discount_ids: | ||
298 | 64 | discount_date = (DateTime.strptime(invoice_data.date_invoice, '%Y-%m-%d') + DateTime.RelativeDateTime(days=dis_line.delay)).strftime('%Y-%m-%d') | ||
299 | 65 | if discount_date > current_date: | ||
300 | 66 | self.write(cr, uid, [id], {'next_payment_date':discount_date}) | ||
301 | 67 | break | ||
302 | 68 | return True | ||
303 | 51 | 69 | ||
304 | 70 | def check_next_date(self, cr, uid, ids=[], context=None): | ||
305 | 71 | self._next_date(cr, uid, ids, context=context) | ||
306 | 72 | |||
307 | 52 | def copy(self, cr, uid, id, default=None, context=None): | 73 | def copy(self, cr, uid, id, default=None, context=None): |
308 | 53 | if default is None: | 74 | if default is None: |
309 | 54 | default = {} | 75 | default = {} |
310 | 55 | 76 | ||
311 | === added file 'account_payment_discount_extension/account_payment_discount_data.xml' | |||
312 | --- account_payment_discount_extension/account_payment_discount_data.xml 1970-01-01 00:00:00 +0000 | |||
313 | +++ account_payment_discount_extension/account_payment_discount_data.xml 2010-09-14 05:41:08 +0000 | |||
314 | @@ -0,0 +1,17 @@ | |||
315 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
316 | 2 | <openerp> | ||
317 | 3 | <data noupdate="1"> | ||
318 | 4 | |||
319 | 5 | <record forcecreate="True" id="ir_cron_discount_action" model="ir.cron"> | ||
320 | 6 | <field name="name">Next Payment Date</field> | ||
321 | 7 | <field name="interval_number">1</field> | ||
322 | 8 | <field name="interval_type">days</field> | ||
323 | 9 | <field name="numbercall">-1</field> | ||
324 | 10 | <field eval="False" name="doall"/> | ||
325 | 11 | <field eval="'account.invoice'" name="model"/> | ||
326 | 12 | <field eval="'check_next_date'" name="function"/> | ||
327 | 13 | <field eval="'(False,)'" name="args"/> | ||
328 | 14 | </record> | ||
329 | 15 | |||
330 | 16 | </data> | ||
331 | 17 | </openerp> | ||
332 | 0 | 18 | ||
333 | === modified file 'account_payment_discount_extension/wizard/wizard_payment_discount_order.py' | |||
334 | --- account_payment_discount_extension/wizard/wizard_payment_discount_order.py 2010-09-20 14:25:59 +0000 | |||
335 | +++ account_payment_discount_extension/wizard/wizard_payment_discount_order.py 2010-09-14 05:41:08 +0000 | |||
336 | @@ -27,6 +27,7 @@ | |||
337 | 27 | from datetime import datetime | 27 | from datetime import datetime |
338 | 28 | from dateutil.relativedelta import relativedelta | 28 | from dateutil.relativedelta import relativedelta |
339 | 29 | from mx import DateTime | 29 | from mx import DateTime |
340 | 30 | |||
341 | 30 | FORM = UpdateableStr() | 31 | FORM = UpdateableStr() |
342 | 31 | 32 | ||
343 | 32 | FIELDS = { | 33 | FIELDS = { |