Merge lp:~julie-w/unifield-server/US-7295 into lp:unifield-server

Proposed by jftempo
Status: Merged
Merged at revision: 5992
Proposed branch: lp:~julie-w/unifield-server/US-7295
Merge into: lp:unifield-server
Diff against target: 1949 lines (+1045/-93)
29 files modified
bin/addons/account_hq_entries/hq_entries.py (+11/-0)
bin/addons/account_hq_entries/wizard/hq_entries_split.py (+3/-1)
bin/addons/analytic_distribution/account_commitment.py (+16/-2)
bin/addons/analytic_distribution/analytic_account_view.xml (+24/-8)
bin/addons/analytic_distribution/analytic_distribution.py (+27/-9)
bin/addons/analytic_distribution/analytic_line.py (+33/-14)
bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py (+5/-1)
bin/addons/analytic_override/__init__.py (+2/-0)
bin/addons/analytic_override/__openerp__.py (+2/-0)
bin/addons/analytic_override/analytic_account.py (+197/-29)
bin/addons/analytic_override/analytic_line.py (+6/-0)
bin/addons/analytic_override/dest_cc_link.py (+174/-0)
bin/addons/analytic_override/dest_cc_link.xml (+49/-0)
bin/addons/analytic_override/multiple_cc_selection_wizard.py (+56/-0)
bin/addons/analytic_override/multiple_cc_selection_wizard.xml (+30/-0)
bin/addons/financing_contract/financing_contract_account_quadruplet.py (+9/-8)
bin/addons/msf_audittrail/__openerp__.py (+1/-0)
bin/addons/msf_audittrail/data/audittrail_dest_cc_link.yml (+39/-0)
bin/addons/msf_doc_import/msf_import_export.py (+118/-11)
bin/addons/msf_doc_import/msf_import_export_conf.py (+3/-1)
bin/addons/msf_homere_interface/hr_payroll.py (+28/-1)
bin/addons/msf_profile/data/patches.xml (+5/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+152/-3)
bin/addons/msf_profile/msf_profile.py (+14/-0)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+1/-0)
bin/addons/sync_client/update.py (+1/-0)
bin/addons/sync_common/common.py (+1/-0)
bin/addons/sync_so/specific_xml_id.py (+24/-0)
bin/osv/orm.py (+14/-5)
To merge this branch: bzr merge lp:~julie-w/unifield-server/US-7295
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+402180@code.launchpad.net
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 'bin/addons/account_hq_entries/hq_entries.py'
2--- bin/addons/account_hq_entries/hq_entries.py 2020-10-09 10:44:13 +0000
3+++ bin/addons/account_hq_entries/hq_entries.py 2021-05-04 08:01:40 +0000
4@@ -43,6 +43,7 @@
5 res = {}
6 logger = netsvc.Logger()
7 ad_obj = self.pool.get('analytic.distribution')
8+ dest_cc_link_obj = self.pool.get('dest.cc.link')
9 # Search MSF Private Fund element, because it's valid with all accounts
10 try:
11 fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
12@@ -86,6 +87,13 @@
13 res[line.id] = 'invalid'
14 logger.notifyChannel('account_hq_entries', netsvc.LOG_WARNING, _('%s: inactive DEST (%s)') % (line.id or '', dest.code or ''))
15 continue
16+ if line.destination_id and line.cost_center_id and line.date and \
17+ dest_cc_link_obj.is_inactive_dcl(cr, uid, line.destination_id.id, line.cost_center_id.id, line.date, context=context):
18+ res[line.id] = 'invalid'
19+ logger.notifyChannel('account_hq_entries', netsvc.LOG_WARNING,
20+ _('%s: inactive combination (%s - %s)') %
21+ (line.id or '', line.destination_id.code or '', line.cost_center_id.code or ''))
22+ continue
23 # G Check
24 if line.analytic_id:
25 fp = self.pool.get('account.analytic.account').browse(cr, uid, line.analytic_id.id, context={'date': line.document_date})
26@@ -484,6 +492,9 @@
27 def _check_cc(self, cr, uid, ids, context=None):
28 """
29 At synchro time sets HQ entry to Not Run if the Cost Center used in the line doesn't exist or is inactive
30+
31+ Note: if the CC is active but the Dest/CC combination is inactive, the sync update is NOT blocked:
32+ the HQ entry will be created with an invalid AD to be fixed before validation.
33 """
34 if isinstance(ids, (int, long)):
35 ids = [ids]
36
37=== modified file 'bin/addons/account_hq_entries/wizard/hq_entries_split.py'
38--- bin/addons/account_hq_entries/wizard/hq_entries_split.py 2020-11-02 17:29:34 +0000
39+++ bin/addons/account_hq_entries/wizard/hq_entries_split.py 2021-05-04 08:01:40 +0000
40@@ -42,7 +42,9 @@
41 # Process
42 for line in self.browse(cr, uid, ids, context=context):
43 res[line.id] = {'state_info': False, 'state': 'none'}
44- state, info = self.pool.get('analytic.distribution').analytic_state_from_info(cr, uid, line.account_id.id, line.destination_id.id, line.cost_center_id.id, line.analytic_id.id, context=context)
45+ state, info = self.pool.get('analytic.distribution').analytic_state_from_info(cr, uid, line.account_id.id, line.destination_id.id,
46+ line.cost_center_id.id, line.analytic_id.id,
47+ posting_date=line.wizard_id.date, context=context)
48 res[line.id].update({'state_info': info, 'state': state,})
49 return res
50
51
52=== modified file 'bin/addons/analytic_distribution/account_commitment.py'
53--- bin/addons/analytic_distribution/account_commitment.py 2020-05-07 10:01:57 +0000
54+++ bin/addons/analytic_distribution/account_commitment.py 2021-05-04 08:01:40 +0000
55@@ -169,6 +169,7 @@
56 aal_obj = self.pool.get('account.analytic.line')
57 curr_obj = self.pool.get('res.currency')
58 user_obj = self.pool.get('res.users')
59+ dest_cc_link_obj = self.pool.get('dest.cc.link')
60 # Browse elements if 'date' in vals
61 if vals.get('date', False):
62 date = vals.get('date')
63@@ -188,8 +189,21 @@
64 raise osv.except_osv(_('Warning'), _('No analytic distribution found for %s %s') % (cl.account_id.code, cl.initial_amount))
65 for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
66 for distrib_line in distrib_lines:
67- if (distrib_line.analytic_id.date_start and date < distrib_line.analytic_id.date_start) or (distrib_line.analytic_id.date and date > distrib_line.analytic_id.date):
68- raise osv.except_osv(_('Error'), _('The analytic account %s is not active for given date.') % (distrib_line.analytic_id.name,))
69+ if distrib_line.analytic_id and \
70+ (distrib_line.analytic_id.date_start and date < distrib_line.analytic_id.date_start or
71+ distrib_line.analytic_id.date and date >= distrib_line.analytic_id.date):
72+ raise osv.except_osv(_('Error'), _('The analytic account %s is not active for given date.') %
73+ (distrib_line.analytic_id.name,))
74+ dest_cc_tuples = set() # check each Dest/CC combination only once
75+ for distrib_cc_l in distrib.cost_center_lines:
76+ if distrib_cc_l.analytic_id: # non mandatory field
77+ dest_cc_tuples.add((distrib_cc_l.destination_id, distrib_cc_l.analytic_id))
78+ for distrib_fp_l in distrib.funding_pool_lines:
79+ dest_cc_tuples.add((distrib_fp_l.destination_id, distrib_fp_l.cost_center_id))
80+ for dest, cc in dest_cc_tuples:
81+ if dest_cc_link_obj.is_inactive_dcl(cr, uid, dest.id, cc.id, date, context=context):
82+ raise osv.except_osv(_('Error'), _("The combination \"%s - %s\" is not active at this date: %s") %
83+ (dest.code or '', cc.code or '', date))
84 # update the dates and fctal amounts of the related analytic lines
85 context.update({'currency_date': date}) # same date used for doc, posting and source date of all lines
86 for aal in cl.analytic_lines:
87
88=== modified file 'bin/addons/analytic_distribution/analytic_account_view.xml'
89--- bin/addons/analytic_distribution/analytic_account_view.xml 2020-10-08 13:11:14 +0000
90+++ bin/addons/analytic_distribution/analytic_account_view.xml 2021-05-04 08:01:40 +0000
91@@ -132,13 +132,28 @@
92 </field>
93 </page>
94 <page string="Cost Centers" attrs="{'invisible': [('category', '!=', 'DEST')]}">
95- <field name="allow_all_cc" colspan="4" on_change="on_change_allow_all_cc(allow_all_cc, dest_cc_ids)"/>
96- <button name="button_dest_cc_clear" type="object" string="Remove all" icon="gtk-clear" colspan="4"/>
97+ <field name="allow_all_cc" colspan="4" on_change="on_change_allow_all_cc(allow_all_cc, dest_cc_link_ids)"/>
98+ <button name="button_dest_cc_clear" type="object" string="Remove all" icon="gtk-clear" colspan="4"
99+ confirm="Do you really want to remove all the Cost Centers selected?"
100+ />
101 <separator/>
102- <field name="dest_cc_ids" nolabel="1" colspan="4" on_change="on_change_cc_ids(dest_cc_ids)">
103- <tree string="Cost Centers">
104- <field name="code"/>
105- <field name="name"/>
106+ <group colspan="6" col="4">
107+ <button icon="gtk-add" string="Add several Cost Centers" colspan="1"
108+ name="open_multiple_cc_selection_wizard" type="object"
109+ context="{'current_destination_id': record_id}"/>
110+ <label string="" colspan="3"/>
111+ </group>
112+ <field name="dest_cc_link_ids" nolabel="1" colspan="4"
113+ on_change="on_change_cc_ids(dest_cc_link_ids)"
114+ context="{'current_destination_id': record_id}">
115+ <tree string="Cost Centers" editable="bottom">
116+ <field name="current_id" invisible="1"/>
117+ <field name="cc_id" string="Code"
118+ on_change="on_change_cc_id(cc_id)"
119+ attrs="{'readonly': [('current_id', '!=', False)]}"/>
120+ <field name="cc_name" string="Name"/>
121+ <field name="active_from"/>
122+ <field name="inactive_from"/>
123 </tree>
124 </field>
125 </page>
126@@ -262,9 +277,10 @@
127 <field name="inherit_id" ref="account.view_account_analytic_account_list"/>
128 <field name="arch" type="xml">
129 <tree string="Analytic Accounts" position="replace">
130- <tree toolbar="1" colors="red:(date and date&lt;=current_date or dest_without_cc==True)"
131- string="Analytic Accounts">
132+ <tree toolbar="1" colors="red:(date and date&lt;=current_date or dest_without_cc==True);grey:selected_in_dest"
133+ notselectable="selected_in_dest" string="Analytic Accounts">
134 <field name="dest_without_cc" invisible="1"/>
135+ <field name="selected_in_dest" invisible="1"/>
136 <field name="name"/>
137 <field name="code"/>
138 <field name="description"/>
139
140=== modified file 'bin/addons/analytic_distribution/analytic_distribution.py'
141--- bin/addons/analytic_distribution/analytic_distribution.py 2020-11-02 17:29:34 +0000
142+++ bin/addons/analytic_distribution/analytic_distribution.py 2021-05-04 08:01:40 +0000
143@@ -36,13 +36,16 @@
144 if context is None:
145 context = {}
146 analytic_acc_obj = self.pool.get('account.analytic.account')
147+ dest_cc_link_obj = self.pool.get('dest.cc.link')
148+ ret = True # by default if either dest or cc is missing
149 if destination_id and cost_center_id:
150- dest = analytic_acc_obj.browse(cr, uid, destination_id, fields_to_fetch=['category', 'allow_all_cc', 'dest_cc_ids'], context=context)
151- cc = analytic_acc_obj.browse(cr, uid, cost_center_id, fields_to_fetch=['category'], context=context)
152- if dest and cc and dest.category == 'DEST' and cc.category == 'OC' and not dest.allow_all_cc and \
153- cc.id not in [c.id for c in dest.dest_cc_ids]:
154- return False
155- return True
156+ if analytic_acc_obj.search_exist(cr, uid, [('id', '=', destination_id), ('allow_all_cc', '=', True)], context=context):
157+ ret = True
158+ elif dest_cc_link_obj.search_exist(cr, uid, [('dest_id', '=', destination_id), ('cc_id', '=', cost_center_id)], context=context):
159+ ret = True
160+ else:
161+ ret = False
162+ return ret
163
164 def check_fp_cc_compatibility(self, cr, uid, fp_id, cost_center_id, context=None):
165 """
166@@ -146,6 +149,7 @@
167 if context is None:
168 context = {}
169 analytic_acc_obj = self.pool.get('account.analytic.account')
170+ dest_cc_link_obj = self.pool.get('dest.cc.link')
171 # Have an analytic distribution on another account than analytic-a-holic account make no sense. So their analytic distribution is valid
172 if account_id:
173 account = self.pool.get('account.account').read(cr, uid, account_id, ['is_analytic_addicted'])
174@@ -153,7 +157,8 @@
175 return 'valid'
176 if not distrib_id:
177 if parent_id:
178- return self._get_distribution_state(cr, uid, parent_id, False, account_id, context, amount=amount)
179+ return self._get_distribution_state(cr, uid, parent_id, False, account_id, context=context,
180+ doc_date=doc_date, posting_date=posting_date, manual=manual, amount=amount)
181 return 'none'
182 distrib = self.browse(cr, uid, distrib_id)
183 if not distrib.funding_pool_lines:
184@@ -183,6 +188,9 @@
185 return 'invalid'
186 if not analytic_acc_obj.is_account_active(fp_line.cost_center_id, posting_date):
187 return 'invalid'
188+ if dest_cc_link_obj.is_inactive_dcl(cr, uid, fp_line.destination_id.id, fp_line.cost_center_id.id,
189+ posting_date, context=context):
190+ return 'invalid'
191 if doc_date and fp_line.analytic_id and not analytic_acc_obj.is_account_active(fp_line.analytic_id, doc_date):
192 return 'invalid'
193 if fp_line.destination_id.id not in account.get('destination_ids', []):
194@@ -206,7 +214,7 @@
195 return 'invalid'
196 return 'valid'
197
198- def analytic_state_from_info(self, cr, uid, account_id, destination_id, cost_center_id, analytic_id, context=None):
199+ def analytic_state_from_info(self, cr, uid, account_id, destination_id, cost_center_id, analytic_id, posting_date=False, context=None):
200 """
201 Give analytic state from the given information.
202 Return result and some info if needed.
203@@ -217,6 +225,7 @@
204 # Prepare some values
205 res = 'valid'
206 info = ''
207+ dest_cc_link_obj = self.pool.get('dest.cc.link')
208 account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
209 # DISTRIBUTION VERIFICATION
210 # Check that destination is compatible with account
211@@ -225,6 +234,9 @@
212 # Check that Destination and Cost Center are compatible
213 if not self.check_dest_cc_compatibility(cr, uid, destination_id, cost_center_id, context=context):
214 return 'invalid', _('Cost Center not compatible with destination')
215+ # Check that their combination is active
216+ if posting_date and dest_cc_link_obj.is_inactive_dcl(cr, uid, destination_id, cost_center_id, posting_date, context=context):
217+ return 'invalid', _('Inactive DEST/CC combination')
218 # Check that cost center is compatible with FP
219 if not self.check_fp_cc_compatibility(cr, uid, analytic_id, cost_center_id, context=context):
220 return 'invalid', _('Cost Center not compatible with FP')
221@@ -236,10 +248,11 @@
222 def check_cc_distrib_active(self, cr, uid, distrib_br, posting_date=False, prefix='', from_supply=False):
223 """
224 Checks the Cost Center Distribution Lines of the distribution in param.:
225- raises an error if the CC or the Dest. used is not active at the posting date selected (or today's date)
226+ raises an error if the CC, the Dest., or their combination is not active at the posting date selected (or today's date)
227 If needed a "prefix" can be added to the error message.
228 """
229 cc_distrib_line_obj = self.pool.get('cost.center.distribution.line')
230+ dest_cc_link_obj = self.pool.get('dest.cc.link')
231 if distrib_br:
232 if not posting_date:
233 posting_date = time.strftime('%Y-%m-%d')
234@@ -257,6 +270,11 @@
235 else:
236 raise osv.except_osv(_('Error'), _('%sDestination %s is either inactive at the date %s, or it allows no Cost Center.') %
237 (prefix, cline.destination_id.code or '', posting_date))
238+ if cline.destination_id and cline.analytic_id and \
239+ dest_cc_link_obj.is_inactive_dcl(cr, uid, cline.destination_id.id, cline.analytic_id.id, posting_date):
240+ raise osv.except_osv(_('Error'), _("%sThe combination \"%s - %s\" is not active at this date: %s") %
241+ (prefix, cline.destination_id.code or '', cline.analytic_id.code or '', posting_date))
242+
243
244
245 analytic_distribution()
246
247=== modified file 'bin/addons/analytic_distribution/analytic_line.py'
248--- bin/addons/analytic_distribution/analytic_line.py 2020-12-01 17:29:45 +0000
249+++ bin/addons/analytic_distribution/analytic_line.py 2021-05-04 08:01:40 +0000
250@@ -413,6 +413,8 @@
251 ids = [ids]
252 # Prepare some value
253 ad_obj = self.pool.get('analytic.distribution')
254+ dest_cc_link_obj = self.pool.get('dest.cc.link')
255+ period_obj = self.pool.get('account.period')
256 account = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['category', 'date_start', 'date'], context=context)
257 account_type = account and account.get('category', False) or False
258 res = []
259@@ -422,6 +424,9 @@
260 date_start = account and account.get('date_start', False) or False
261 date_stop = account and account.get('date', False) or False
262 # Date verification for all lines and fetch all necessary elements sorted by analytic distribution
263+ cmp_dates = {}
264+ wiz_period_open = period_obj.search_exist(cr, uid, [('date_start', '<=', wiz_date), ('date_stop', '>=', wiz_date),
265+ ('special', '=', False), ('state', '=', 'draft')], context=context)
266 for aline in self.browse(cr, uid, ids):
267 # UTP-800: Change date comparison regarding FP. If FP, use document date. Otherwise use date.
268 aline_cmp_date = aline.date
269@@ -435,13 +440,25 @@
270 if aline.journal_id.type == 'hq' or aline.period_id and aline.period_id.state in ['done', 'mission-closed']:
271 aline_cmp_date = wiz_date
272 # these lines will be reverted, check if the reverted line is active
273- oc_dest_date_start = max(aline.cost_center_id.date_start, aline.destination_id.date_start)
274- oc_dest_date_stop = min(aline.cost_center_id.date or '9999-01-01', aline.destination_id.date or '9999-01-01')
275- if (oc_dest_date_start and wiz_date < oc_dest_date_start) or (oc_dest_date_stop and wiz_date >= oc_dest_date_stop):
276+ if not wiz_period_open:
277 expired_date_ids.append(aline.id)
278+ else:
279+ oc_dest_date_start = max(aline.cost_center_id.date_start, aline.destination_id.date_start)
280+ oc_dest_date_stop = min(aline.cost_center_id.date or '9999-01-01', aline.destination_id.date or '9999-01-01')
281+ if (oc_dest_date_start and wiz_date < oc_dest_date_start) or (oc_dest_date_stop and wiz_date >= oc_dest_date_stop):
282+ expired_date_ids.append(aline.id)
283+ else:
284+ # check the Dest/CC link validity with the original Dest and CC which will be used in the REV
285+ destination_id = aline.destination_id and aline.destination_id.id or False
286+ cost_center_id = aline.cost_center_id and aline.cost_center_id.id or False
287+ if destination_id and cost_center_id and \
288+ dest_cc_link_obj.is_inactive_dcl(cr, uid, destination_id, cost_center_id, wiz_date, context=context):
289+ expired_date_ids.append(aline.id)
290 if (date_start and aline_cmp_date < date_start) or (date_stop and aline_cmp_date >= date_stop):
291 expired_date_ids.append(aline.id)
292+ cmp_dates[aline.id] = aline_cmp_date
293 # Process regarding account_type
294+ ids = [i for i in ids if i not in expired_date_ids] # exclude the AJI in expired_date_ids
295 if account_type == 'OC':
296 for aline in self.browse(cr, uid, ids):
297 # Verify that:
298@@ -449,10 +466,11 @@
299 check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
300 if check_accounts and aline.account_id.id in check_accounts:
301 continue
302- if ad_obj.check_dest_cc_compatibility(cr, uid, aline.destination_id and aline.destination_id.id or False,
303- account_id, context=context):
304- if ad_obj.check_fp_cc_compatibility(cr, uid, aline.account_id.id, account_id, context=context):
305- res.append(aline.id)
306+ dest_id = aline.destination_id and aline.destination_id.id or False
307+ if ad_obj.check_dest_cc_compatibility(cr, uid, dest_id, account_id, context=context) and \
308+ ad_obj.check_fp_cc_compatibility(cr, uid, aline.account_id.id, account_id, context=context) and \
309+ not dest_cc_link_obj.is_inactive_dcl(cr, uid, dest_id, account_id, cmp_dates[aline.id], context=context):
310+ res.append(aline.id)
311 elif account_type == 'FUNDING':
312 # Browse all analytic line to verify them
313 for aline in self.browse(cr, uid, ids):
314@@ -475,19 +493,16 @@
315 for aline in self.browse(cr, uid, ids, context=context):
316 # the following check is included into check_fp_acc_dest_compatibility:
317 # account_id in [x.id for x in aline.general_account_id.destination_ids]
318- if ad_obj.check_dest_cc_compatibility(cr, uid, account_id, aline.cost_center_id and aline.cost_center_id.id or False,
319- context=context) and \
320+ cc_id = aline.cost_center_id and aline.cost_center_id.id or False
321+ if ad_obj.check_dest_cc_compatibility(cr, uid, account_id, cc_id, context=context) and \
322 ad_obj.check_fp_acc_dest_compatibility(cr, uid, aline.account_id.id, aline.general_account_id.id,
323- account_id, context=context):
324+ account_id, context=context) and \
325+ not dest_cc_link_obj.is_inactive_dcl(cr, uid, account_id, cc_id, cmp_dates[aline.id], context=context):
326 res.append(aline.id)
327 else:
328 # Case of FREE1 and FREE2 lines
329 for i in ids:
330 res.append(i)
331- # Delete elements that are in expired_date_ids
332- for e in expired_date_ids:
333- if e in res:
334- res.remove(e)
335 return res
336
337 def check_dest_cc_fp_compatibility(self, cr, uid, ids,
338@@ -513,6 +528,7 @@
339 new_cc_id, new_cc_br,
340 new_fp_id, new_fp_br):
341 ad_obj = self.pool.get('analytic.distribution')
342+ dest_cc_link_obj = self.pool.get('dest.cc.link')
343 if not general_account_br.is_analytic_addicted:
344 res.append((id, entry_sequence, ''))
345 return False
346@@ -549,6 +565,9 @@
347 if not check_date(new_cc_br, posting_date):
348 res.append((id, entry_sequence, _('CC date')))
349 return False
350+ if new_dest_id and new_cc_id and dest_cc_link_obj.is_inactive_dcl(cr, uid, new_dest_id, new_cc_id, posting_date, context=context):
351+ res.append((id, entry_sequence, _('DEST/CC combination date')))
352+ return False
353 if new_fp_id != msf_pf_id and not \
354 check_date(new_fp_br, posting_date):
355 res.append((id, entry_sequence, _('FP date')))
356
357=== modified file 'bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py'
358--- bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2020-10-09 12:26:23 +0000
359+++ bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2021-05-04 08:01:40 +0000
360@@ -1006,6 +1006,7 @@
361 if isinstance(ids, (int, long)):
362 ids = [ids]
363 distrib_obj = self.pool.get('analytic.distribution')
364+ dest_cc_link_obj = self.pool.get('dest.cc.link')
365 for w in self.browse(cr, uid, ids):
366 # UF-1678
367 # For Cost center and destination analytic accounts, check is done on POSTING date. It HAVE TO BE in context to be well processed (filter_active is a function that need a context)
368@@ -1019,6 +1020,9 @@
369 if not fpline.destination_id.filter_active:
370 raise osv.except_osv(_('Error'), _('Destination %s is either inactive at the date %s, or it allows no Cost Center.')
371 % (fpline.destination_id.code or '', w.posting_date))
372+ if dest_cc_link_obj.is_inactive_dcl(cr, uid, fpline.destination_id.id, fpline.cost_center_id.id, w.posting_date):
373+ raise osv.except_osv(_('Error'), _("The combination \"%s - %s\" is not active at this date: %s") %
374+ (fpline.destination_id.code or '', fpline.cost_center_id.code or '', w.posting_date))
375 # UF-1678
376 # For funding pool analytic account, check is done on DOCUMENT date. It HAVE TO BE in context to be well processed (filter_active is a function that need a context)
377 if w.distribution_id and w.document_date:
378@@ -1079,7 +1083,7 @@
379 self.wizard_verifications(cr, uid, wiz.id, context=context)
380 # And do distribution creation if necessary
381 distrib_id = wiz.distribution_id and wiz.distribution_id.id or False
382- if not distrib_id:
383+ if not distrib_id or not self.pool.get('analytic.distribution').exists(cr, uid, distrib_id, context=context):
384 # create a new analytic distribution
385 analytic_vals = {}
386 if wiz.partner_type:#UF-2138: added the ref to partner type of FO/PO
387
388=== modified file 'bin/addons/analytic_override/__init__.py'
389--- bin/addons/analytic_override/__init__.py 2014-03-14 09:40:12 +0000
390+++ bin/addons/analytic_override/__init__.py 2021-05-04 08:01:40 +0000
391@@ -22,5 +22,7 @@
392 import analytic_distribution
393 import analytic_account
394 import analytic_line
395+import dest_cc_link
396+import multiple_cc_selection_wizard
397
398 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
399\ No newline at end of file
400
401=== modified file 'bin/addons/analytic_override/__openerp__.py'
402--- bin/addons/analytic_override/__openerp__.py 2014-10-29 13:47:46 +0000
403+++ bin/addons/analytic_override/__openerp__.py 2021-05-04 08:01:40 +0000
404@@ -32,6 +32,8 @@
405 "init_xml" : [],
406 "update_xml": [
407 'security/ir.model.access.csv',
408+ 'dest_cc_link.xml',
409+ 'multiple_cc_selection_wizard.xml',
410 ],
411 'test': [],
412 'demo_xml': [
413
414=== modified file 'bin/addons/analytic_override/analytic_account.py'
415--- bin/addons/analytic_override/analytic_account.py 2020-10-30 17:31:31 +0000
416+++ bin/addons/analytic_override/analytic_account.py 2021-05-04 08:01:40 +0000
417@@ -40,8 +40,8 @@
418 if context is None:
419 context = {}
420 res = {}
421- for a in self.browse(cr, uid, ids, fields_to_fetch=['category', 'type', 'allow_all_cc', 'dest_cc_ids'], context=context):
422- if a.category == 'DEST' and a.type == 'normal' and not a.allow_all_cc and not a.dest_cc_ids:
423+ for a in self.browse(cr, uid, ids, fields_to_fetch=['category', 'type', 'allow_all_cc', 'dest_cc_link_ids'], context=context):
424+ if a.category == 'DEST' and a.type != 'view' and not a.allow_all_cc and not a.dest_cc_link_ids:
425 res[a.id] = True
426 else:
427 res[a.id] = False
428@@ -95,7 +95,7 @@
429 arg.append(('category', '!=', 'DEST'))
430 arg.append(('type', '=', 'view'))
431 arg.append(('allow_all_cc', '=', True))
432- arg.append(('dest_cc_ids', '!=', False))
433+ arg.append(('dest_cc_link_ids', '!=', False))
434 # filter: inactive
435 elif x[0] == 'filter_active' and x[2] is False:
436 arg.append('|')
437@@ -106,9 +106,9 @@
438 arg.append('&')
439 arg.append('&')
440 arg.append(('category', '=', 'DEST'))
441- arg.append(('type', '=', 'normal'))
442+ arg.append(('type', '!=', 'view'))
443 arg.append(('allow_all_cc', '=', False))
444- arg.append(('dest_cc_ids', '=', False))
445+ arg.append(('dest_cc_link_ids', '=', False))
446 return arg
447
448 def _get_fake(self, cr, uid, ids, *a, **b):
449@@ -282,11 +282,20 @@
450 cc = arg[2]
451 if operator != '=' or not isinstance(cc, (int, long)):
452 raise osv.except_osv(_('Error'), _('Filter not implemented on Destinations.'))
453- all_dest_ids = self.search(cr, uid, [('category', '=', 'DEST')], context=context)
454- compatible_dest_ids = []
455- for dest in self.browse(cr, uid, all_dest_ids, fields_to_fetch=['allow_all_cc', 'dest_cc_ids'], context=context):
456- if dest.allow_all_cc or (cc and cc in [c.id for c in dest.dest_cc_ids]):
457- compatible_dest_ids.append(dest.id)
458+ if not cc:
459+ # by default if no CC is selected display only the Destinations compatible with all CC
460+ compatible_dest_ids = self.search(cr, uid, [('category', '=', 'DEST'),
461+ ('type', '!=', 'view'),
462+ ('allow_all_cc', '=', True)], context=context)
463+ else:
464+ compatible_dest_sql = """
465+ SELECT id
466+ FROM account_analytic_account
467+ WHERE category = 'DEST' AND type != 'view'
468+ AND (allow_all_cc = 't' OR id IN (SELECT dest_id FROM dest_cc_link WHERE cc_id = %s));
469+ """
470+ cr.execute(compatible_dest_sql, (cc,))
471+ compatible_dest_ids = [x[0] for x in cr.fetchall()]
472 dom.append(('id', 'in', compatible_dest_ids))
473 return dom
474
475@@ -442,6 +451,54 @@
476 }
477 return res
478
479+ def _get_selected_in_dest(self, cr, uid, cc_ids, name=False, args=False, context=None):
480+ """
481+ Returns True for the Cost Centers already selected in the Destination:
482+ they will be displayed in grey in the list and won't be re-selectable.
483+ """
484+ if context is None:
485+ context = {}
486+ if isinstance(cc_ids, (int, long)):
487+ cc_ids = [cc_ids]
488+ selected = []
489+ dest_id = context.get('current_destination_id') or False
490+ if dest_id:
491+ dest = self.browse(cr, uid, dest_id, fields_to_fetch=['dest_cc_link_ids'], context=context)
492+ selected = [dest_cc_link.cc_id.id for dest_cc_link in dest.dest_cc_link_ids]
493+ res = {}
494+ for cc_id in cc_ids:
495+ res[cc_id] = cc_id in selected
496+ return res
497+
498+ def _get_dest_cc_link_dates(self, cr, uid, ids, field_name, args, context=None):
499+ """
500+ Returns a dict with key = id of the analytic account,
501+ and value = dict with dest_cc_link_active_from and dest_cc_link_inactive_from dates separated by commas (String).
502+ Note that the date format is the same in EN and FR, and that empty dates are not ignored.
503+ E.g.: '2021-03-02,2021-03-01,,2021-03-03,'
504+
505+ This is used in Destination Import Tools, in particular for the Export of existing entries used as examples.
506+ """
507+ if context is None:
508+ context = {}
509+ if isinstance(ids, (int, long)):
510+ ids = [ids]
511+ res = {}
512+ for a in self.browse(cr, uid, ids, fields_to_fetch=['category', 'dest_cc_link_ids'], context=context):
513+ active_date_list = []
514+ inactive_date_list = []
515+ if a.category == 'DEST':
516+ for cc_link in a.dest_cc_link_ids:
517+ active_date_str = "%s" % (cc_link.active_from or "")
518+ active_date_list.append(active_date_str)
519+ inactive_date_str = "%s" % (cc_link.inactive_from or "")
520+ inactive_date_list.append(inactive_date_str)
521+ res[a.id] = {
522+ 'dest_cc_link_active_from': ",".join(active_date_list),
523+ 'dest_cc_link_inactive_from': ",".join(inactive_date_list),
524+ }
525+ return res
526+
527 _columns = {
528 'name': fields.char('Name', size=128, required=True, translate=1),
529 'code': fields.char('Code', size=24),
530@@ -463,6 +520,17 @@
531 'dest_cc_ids': fields.many2many('account.analytic.account', 'destination_cost_center_rel',
532 'destination_id', 'cost_center_id', string='Cost Centers',
533 domain="[('type', '!=', 'view'), ('category', '=', 'OC')]"),
534+ 'dest_cc_link_ids': fields.one2many('dest.cc.link', 'dest_id', string="Cost Centers", required=False),
535+ 'dest_cc_link_active_from': fields.function(_get_dest_cc_link_dates, method=True, type='char',
536+ store=False, readonly=True,
537+ string='Activation Combination Dest / CC from',
538+ help="Technical field used for Import Tools only",
539+ multi="dest_cc_link_dates"),
540+ 'dest_cc_link_inactive_from': fields.function(_get_dest_cc_link_dates, method=True, type='char',
541+ store=False, readonly=True,
542+ string='Inactivation Combination Dest / CC from',
543+ help="Technical field used for Import Tools only",
544+ multi="dest_cc_link_dates"),
545 'allow_all_cc': fields.boolean(string="Allow all Cost Centers"), # for the Destinations
546 'allow_all_cc_with_fp': fields.boolean(string="Allow all Cost Centers"), # for the Funding Pools
547 'dest_compatible_with_cc_ids': fields.function(_get_fake, method=True, store=False,
548@@ -498,6 +566,8 @@
549 'fp_account_ids': fields.many2many('account.account', 'fp_account_rel', 'fp_id', 'account_id', string='G/L Accounts',
550 domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True), ('active', '=', 't')]",
551 help="G/L accounts linked to the Funding Pool", order_by='code'),
552+ 'selected_in_dest': fields.function(_get_selected_in_dest, string='Selected in Destination', method=True,
553+ type='boolean', store=False),
554 }
555
556 _defaults ={
557@@ -577,39 +647,49 @@
558 res['domain']['parent_id'] = [('category', '=', category), ('type', '=', 'view')]
559 return res
560
561- def on_change_allow_all_cc(self, cr, uid, ids, allow_all_cc, cc_ids, acc_type='destination', field_name='allow_all_cc', context=None):
562+ def on_change_allow_all_cc(self, cr, uid, ids, allow_all_cc, cc_ids, acc_type='destination', field_name='allow_all_cc',
563+ m2m=False, context=None):
564 """
565 If the user tries to tick the box "Allow all Cost Centers" whereas CC are selected,
566 informs him that he has to remove the CC first
567 (acc_type = name of the Analytic Account Type to which the CC are linked, displayed in the warning msg)
568 """
569 res = {}
570- if allow_all_cc and cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
571- # NOTE: the msg is stored in a variable on purpose, otherwise the ".po" translation files would wrongly contain Python code
572- msg = 'Please remove the Cost Centers linked to the %s before ticking this box.' % acc_type.title()
573- warning = {
574- 'title': _('Warning!'),
575- 'message': _(msg)
576- }
577- res['warning'] = warning
578- res['value'] = {field_name: False, }
579+ if allow_all_cc:
580+ if m2m:
581+ cc_filled_in = cc_ids and cc_ids[0][2] or False # e.g. [(6, 0, [1, 2])]
582+ else:
583+ cc_filled_in = cc_ids or False
584+ if cc_filled_in:
585+ # NOTE: the msg is stored in a variable on purpose, otherwise the ".po" translation files would wrongly contain Python code
586+ msg = 'Please remove the Cost Centers linked to the %s before ticking this box.' % acc_type.title()
587+ warning = {
588+ 'title': _('Warning!'),
589+ 'message': _(msg)
590+ }
591+ res['warning'] = warning
592+ res['value'] = {field_name: False, }
593 return res
594
595 def on_change_allow_all_cc_with_fp(self, cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, context=None):
596 return self.on_change_allow_all_cc(cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, acc_type='funding pool',
597- field_name='allow_all_cc_with_fp', context=context)
598+ field_name='allow_all_cc_with_fp', m2m=True, context=context)
599
600- def on_change_cc_ids(self, cr, uid, ids, cc_ids, field_name='allow_all_cc', context=None):
601+ def on_change_cc_ids(self, cr, uid, ids, cc_ids, field_name='allow_all_cc', m2m=False, context=None):
602 """
603 If at least a CC is selected, unticks the box "Allow all Cost Centers"
604 """
605 res = {}
606- if cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
607+ if m2m:
608+ cc_filled_in = cc_ids and cc_ids[0][2] or False # e.g. [(6, 0, [1, 2])]
609+ else:
610+ cc_filled_in = cc_ids or False
611+ if cc_filled_in:
612 res['value'] = {field_name: False, }
613 return res
614
615 def on_change_cc_with_fp(self, cr, uid, ids, cost_center_ids, context=None):
616- return self.on_change_cc_ids(cr, uid, ids, cost_center_ids, field_name='allow_all_cc_with_fp', context=context)
617+ return self.on_change_cc_ids(cr, uid, ids, cost_center_ids, field_name='allow_all_cc_with_fp', m2m=True, context=context)
618
619 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
620 if not context:
621@@ -685,6 +765,7 @@
622 if vals['category'] != 'DEST':
623 vals['destination_ids'] = [(6, 0, [])]
624 vals['dest_cc_ids'] = [(6, 0, [])]
625+ vals['dest_cc_link_ids'] = [] # related dest.cc.links (if any) are deleted in _clean_dest_cc_link
626 vals['allow_all_cc'] = False # default value
627 if vals['category'] != 'FUNDING':
628 vals['tuple_destination_account_ids'] = [(6, 0, [])]
629@@ -727,6 +808,7 @@
630 default['tuple_destination_summary'] = []
631 default['line_ids'] = []
632 default['dest_cc_ids'] = []
633+ default['dest_cc_link_ids'] = []
634 return super(analytic_account, self).copy(cr, uid, a_id, default, context=context)
635
636 def _check_name_unicity(self, cr, uid, ids, context=None):
637@@ -785,6 +867,57 @@
638 self.log(cr, uid, analytic_account_id, _('At least one Analytic Journal Item using the Analytic Account %s '
639 'has a Posting Date outside the activation dates selected.') % (analytic_acc.code))
640
641+ def _clean_dest_cc_link(self, cr, uid, ids, vals, context=None):
642+ """
643+ In case Dest CC Links are reset in an analytic account: deletes the related existing Dest CC Links if any.
644+ Probable UC: Dest CC Links selected on a destination, then account changed to another category.
645+ """
646+ if context is None:
647+ context = {}
648+ if isinstance(ids, (int, long)):
649+ ids = [ids]
650+ if 'dest_cc_link_ids' in vals and not vals['dest_cc_link_ids']:
651+ dcl_ids = []
652+ for analytic_acc in self.browse(cr, uid, ids, fields_to_fetch=['dest_cc_link_ids'], context=context):
653+ dcl_ids.extend([dcl.id for dcl in analytic_acc.dest_cc_link_ids])
654+ if dcl_ids:
655+ self.pool.get('dest.cc.link').unlink(cr, uid, dcl_ids, context=context)
656+ return True
657+
658+ def _dest_cc_ids_must_be_updated(self, vals, context):
659+ """
660+ Returns True if dest_cc_ids in vals must be changed to dest_cc_link_ids (the goal of this method is to ensure
661+ that the same condition is used everywhere and that the UC where all CC are removed is taken into account)
662+ """
663+ if context and vals and context.get('sync_update_execution') and vals.get('dest_cc_ids') and vals['dest_cc_ids'][0][2] is not None:
664+ return True
665+ return False
666+
667+ def _update_synched_dest_cc_ids(self, cr, uid, dest_ids, vals, context):
668+ """
669+ For synch made before or while US-7295 was released: changes the dest_cc_ids into dest_cc_link_ids
670+ """
671+ if self._dest_cc_ids_must_be_updated(vals, context):
672+ dest_cc_link_obj = self.pool.get('dest.cc.link')
673+ if isinstance(dest_ids, (int, long)):
674+ dest_ids = [dest_ids]
675+ for dest_id in dest_ids:
676+ dest = self.browse(cr, uid, dest_id, fields_to_fetch=['dest_cc_link_ids'], context=context)
677+ # note: after US-7295 patch script no instance has any dest_cc_ids, all CC links are necessarily dest.cc.link
678+ current_cc_ids = [dest_cc_link.cc_id.id for dest_cc_link in dest.dest_cc_link_ids]
679+ new_cc_ids = vals['dest_cc_ids'][0][2] or [] # take into account the UC where all CC are removed
680+ # delete the CC to be deleted
681+ cc_to_be_deleted = [c for c in current_cc_ids if c not in new_cc_ids]
682+ if cc_to_be_deleted:
683+ dcl_to_be_deleted = dest_cc_link_obj.search(cr, uid, [('dest_id', '=', dest_id), ('cc_id', 'in', cc_to_be_deleted)],
684+ order='NO_ORDER', context=context)
685+ dest_cc_link_obj.unlink(cr, uid, dcl_to_be_deleted, context=context)
686+ # create the CC to be created
687+ for cc_id in [c for c in new_cc_ids if c not in current_cc_ids]:
688+ dest_cc_link_obj.create(cr, uid, {'dest_id': dest_id, 'cc_id': cc_id}, context=context)
689+ del vals['dest_cc_ids']
690+ return True
691+
692 def create(self, cr, uid, vals, context=None):
693 """
694 Some verifications before analytic account creation
695@@ -797,6 +930,9 @@
696 self._check_date(vals)
697 self.set_funding_pool_parent(cr, uid, vals)
698 vals = self.remove_inappropriate_links(vals, context=context)
699+ vals_copy = vals.copy()
700+ if self._dest_cc_ids_must_be_updated(vals, context):
701+ del vals['dest_cc_ids'] # replaced by dest_cc_link_ids in _update_synched_dest_cc_ids (called after create as it uses the new id)
702 # for auto instance creation, fx gain has been stored, need HQ sync + instance sync to get CC
703 if context.get('sync_update_execution') and vals.get('code') and vals.get('category') == 'OC':
704 param = self.pool.get('ir.config_parameter')
705@@ -804,9 +940,11 @@
706 if init_cc_fx_gain and vals.get('code') == init_cc_fx_gain:
707 vals['for_fx_gain_loss'] = True
708 param.set_param(cr, 1, 'INIT_CC_FX_GAIN', '')
709- ids = super(analytic_account, self).create(cr, uid, vals, context=context)
710- self._check_name_unicity(cr, uid, ids, context=context)
711- return ids
712+ analytic_acc_id = super(analytic_account, self).create(cr, uid, vals, context=context)
713+ self._check_name_unicity(cr, uid, analytic_acc_id, context=context)
714+ self._clean_dest_cc_link(cr, uid, analytic_acc_id, vals, context=context)
715+ self._update_synched_dest_cc_ids(cr, uid, analytic_acc_id, vals_copy, context)
716+ return analytic_acc_id
717
718 def write(self, cr, uid, ids, vals, context=None):
719 """
720@@ -822,7 +960,9 @@
721 self._check_date(vals)
722 self.set_funding_pool_parent(cr, uid, vals)
723 vals = self.remove_inappropriate_links(vals, context=context)
724+ self._update_synched_dest_cc_ids(cr, uid, ids, vals, context)
725 res = super(analytic_account, self).write(cr, uid, ids, vals, context=context)
726+ self._clean_dest_cc_link(cr, uid, ids, vals, context=context)
727 self.check_access_rule(cr, uid, ids, 'write', context=context)
728 if context.get('from_web', False) or context.get('from_import_menu', False):
729 cat_instance = self.read(cr, uid, ids, ['category', 'instance_id', 'is_pf'], context=context)[0]
730@@ -919,9 +1059,15 @@
731
732 def button_dest_cc_clear(self, cr, uid, ids, context=None):
733 """
734- Removes all Cost Centers selected in the Destination view
735+ Removes all Dest / CC combinations selected in the Cost Centers tab of the Destination form
736 """
737- self.write(cr, uid, ids, {'dest_cc_ids': [(6, 0, [])]}, context=context)
738+ if context is None:
739+ context = {}
740+ dest_cc_link_obj = self.pool.get('dest.cc.link')
741+ for dest in self.browse(cr, uid, ids, fields_to_fetch=['dest_cc_link_ids'], context=context):
742+ dest_cc_link_ids = [dcl.id for dcl in dest.dest_cc_link_ids]
743+ if dest_cc_link_ids:
744+ dest_cc_link_obj.unlink(cr, uid, dest_cc_link_ids, context=context)
745 return True
746
747 def button_dest_clear(self, cr, uid, ids, context=None):
748@@ -968,6 +1114,28 @@
749 return False
750 return True
751
752+ def open_multiple_cc_selection_wizard(self, cr, uid, ids, context=None):
753+ """
754+ Creates and displays a Multiple CC Selection Wizard linked to the current Destination
755+ """
756+ if context is None:
757+ context = {}
758+ if isinstance(ids, (int, long)):
759+ ids = [ids]
760+ multiple_cc_wiz_obj = self.pool.get('multiple.cc.selection.wizard')
761+ if ids:
762+ multiple_cc_wiz_id = multiple_cc_wiz_obj.create(cr, uid, {'dest_id': ids[0]}, context=context)
763+ return {
764+ 'type': 'ir.actions.act_window',
765+ 'res_model': 'multiple.cc.selection.wizard',
766+ 'view_type': 'form',
767+ 'view_mode': 'form',
768+ 'target': 'new',
769+ 'res_id': [multiple_cc_wiz_id],
770+ 'context': context,
771+ }
772+ return True
773+
774
775 analytic_account()
776 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
777
778=== modified file 'bin/addons/analytic_override/analytic_line.py'
779--- bin/addons/analytic_override/analytic_line.py 2021-04-23 12:31:26 +0000
780+++ bin/addons/analytic_override/analytic_line.py 2021-05-04 08:01:40 +0000
781@@ -159,6 +159,7 @@
782 return True
783
784 account_obj = self.pool.get('account.analytic.account')
785+ dest_cc_link_obj = self.pool.get('dest.cc.link')
786
787 #US-419: Use the document date and not posting date when checking the validity of analytic account
788 # tech: replaced all date by document_date
789@@ -171,6 +172,8 @@
790 raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (account.name or '',))
791 if 'date' in vals and vals['date'] is not False:
792 date = vals['date']
793+ dest = False
794+ cc = False
795 if vals.get('cost_center_id', False):
796 cc = account_obj.browse(cr, uid, vals['cost_center_id'], context=context)
797 if date < cc.date_start or (cc.date != False and date >= cc.date):
798@@ -181,6 +184,9 @@
799 if date < dest.date_start or (dest.date != False and date >= dest.date):
800 if 'from' not in context or context.get('from') != 'mass_reallocation':
801 raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (dest.name or '',))
802+ if context.get('from') != 'mass_reallocation' and dest and cc and \
803+ dest_cc_link_obj.is_inactive_dcl(cr, uid, dest.id, cc.id, date, context=context):
804+ raise osv.except_osv(_('Error'), _("The combination \"%s - %s\" is not active.") % (dest.code or '', cc.code or ''))
805 return True
806
807 def _check_document_date(self, cr, uid, ids):
808
809=== added file 'bin/addons/analytic_override/dest_cc_link.py'
810--- bin/addons/analytic_override/dest_cc_link.py 1970-01-01 00:00:00 +0000
811+++ bin/addons/analytic_override/dest_cc_link.py 2021-05-04 08:01:40 +0000
812@@ -0,0 +1,174 @@
813+# -*- coding: utf-8 -*-
814+##############################################################################
815+#
816+# OpenERP, Open Source Management Solution
817+# Copyright (C) 2021 MSF, TeMPO Consulting.
818+#
819+# This program is free software: you can redistribute it and/or modify
820+# it under the terms of the GNU Affero General Public License as
821+# published by the Free Software Foundation, either version 3 of the
822+# License, or (at your option) any later version.
823+#
824+# This program is distributed in the hope that it will be useful,
825+# but WITHOUT ANY WARRANTY; without even the implied warranty of
826+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
827+# GNU Affero General Public License for more details.
828+#
829+# You should have received a copy of the GNU Affero General Public License
830+# along with this program. If not, see <http://www.gnu.org/licenses/>.
831+#
832+##############################################################################
833+
834+from osv import osv
835+from osv import fields
836+from tools.translate import _
837+
838+
839+class dest_cc_link(osv.osv):
840+ _name = "dest.cc.link"
841+ _description = "Destination / Cost Center Combination"
842+ _rec_name = "cc_id"
843+ _trace = True
844+
845+ def _get_current_id(self, cr, uid, ids, field_name, args, context=None):
846+ """
847+ Returns a dict with key = value = current DB id.
848+
849+ current_id is an internal field used to make the CC read-only except for new lines (= without DB id).
850+ The goal is to prevent the edition of a Dest CC Link with a CC linked to a different coordo than the previous CC.
851+ """
852+ res = {}
853+ for i in ids:
854+ res[i] = i
855+ return res
856+
857+ def _get_cc_code(self, cr, uid, ids, name, args, context=None):
858+ """
859+ Returns a dict with key = Dest CC Link id, and value = related Cost Center code.
860+ """
861+ if context is None:
862+ context = {}
863+ if isinstance(ids, (int, long)):
864+ ids = [ids]
865+ res = {}
866+ for dcl in self.browse(cr, uid, ids, fields_to_fetch=['cc_id'], context=context):
867+ res[dcl.id] = dcl.cc_id.code or ''
868+ return res
869+
870+ def _get_dest_cc_link_to_update(self, cr, uid, analytic_acc_ids, context=None):
871+ """
872+ Returns the list of Dest CC Links for which the CC code should be updated.
873+ """
874+ if context is None:
875+ context = {}
876+ if isinstance(analytic_acc_ids, (int, long)):
877+ analytic_acc_ids = [analytic_acc_ids]
878+ return self.pool.get('dest.cc.link').search(cr, uid, [('cc_id', 'in', analytic_acc_ids)], order='NO_ORDER', context=context)
879+
880+ _columns = {
881+ 'dest_id': fields.many2one('account.analytic.account', string="Destination", required=True,
882+ domain="[('category', '=', 'DEST'), ('type', '!=', 'view')]", ondelete='cascade', select=1),
883+ 'cc_id': fields.many2one('account.analytic.account', string="Cost Center", required=True, sort_column='cc_code',
884+ domain="[('category', '=', 'OC'), ('type', '!=', 'view')]", ondelete='cascade', select=1),
885+ 'cc_code': fields.function(_get_cc_code, method=True, string="Cost Center Code", type='char', size=24,
886+ readonly=True,
887+ store={
888+ 'account.analytic.account': (_get_dest_cc_link_to_update, ['code'], 10),
889+ 'dest.cc.link': (lambda self, cr, uid, ids, c=None: ids, ['cc_id'], 20),
890+ }),
891+ 'cc_name': fields.related('cc_id', 'name', type="char", string="Cost Center Name", readonly=True, write_relate=False, store=False),
892+ 'active_from': fields.date('Activation Combination Dest / CC from', required=False),
893+ 'inactive_from': fields.date('Inactivation Combination Dest / CC from', required=False),
894+ 'current_id': fields.function(_get_current_id, method=1, type='integer', internal=1, string="DB Id (used by the UI)"),
895+ }
896+
897+ _order = 'dest_id, cc_code, id'
898+
899+ _sql_constraints = [
900+ ('dest_cc_uniq', 'UNIQUE(dest_id, cc_id)', 'Each Cost Center can only be added once to the same Destination.'),
901+ ('dest_cc_date_check', 'CHECK(active_from < inactive_from)', 'The Activation date of the "Combination Dest / CC" '
902+ 'must be before the Inactivation date.')
903+ ]
904+
905+ def _check_analytic_lines(self, cr, uid, ids, context=None):
906+ """
907+ Displays a non-blocking message on the top of the page in case some AJI using the Dest/CC link have been booked
908+ outside its activation dates.
909+ """
910+ if context is None:
911+ context = {}
912+ if not context.get('sync_update_execution'):
913+ aal_obj = self.pool.get('account.analytic.line')
914+ if isinstance(ids, (int, long)):
915+ ids = [ids]
916+ for dcl in self.browse(cr, uid, ids, context=context):
917+ if dcl.active_from or dcl.inactive_from:
918+ dcl_dom = [('cost_center_id', '=', dcl.cc_id.id), ('destination_id', '=', dcl.dest_id.id)]
919+ if dcl.active_from and dcl.inactive_from:
920+ dcl_dom.append('|')
921+ if dcl.active_from:
922+ dcl_dom.append(('date', '<', dcl.active_from))
923+ if dcl.inactive_from:
924+ dcl_dom.append(('date', '>=', dcl.inactive_from))
925+ if aal_obj.search_exist(cr, uid, dcl_dom, context=context):
926+ self.log(cr, uid, dcl.id, _('At least one Analytic Journal Item using the combination \"%s - %s\" '
927+ 'has a Posting Date outside the activation dates selected.') %
928+ (dcl.dest_id.code or '', dcl.cc_id.code or ''))
929+
930+ def create(self, cr, uid, vals, context=None):
931+ """
932+ Creates the Dest CC Combination, and:
933+ - displays an informative message on the top of the page if existing AJIs are using the combination outside its activation interval.
934+ - unticks the box "Allow all Cost Centers" from the related Dest.
935+ (UC: edit a Dest. having the box ticked, untick the box, add a CC and click on Cancel.
936+ CC isn't removed by the Cancel button as it is a o2m, so the box should remain unticked.)
937+ """
938+ if context is None:
939+ context = {}
940+ analytic_acc_obj = self.pool.get('account.analytic.account')
941+ res = super(dest_cc_link, self).create(cr, uid, vals, context=context)
942+ self._check_analytic_lines(cr, uid, res, context=context)
943+ dest_id = self.read(cr, uid, res, ['dest_id'], context=context)['dest_id'][0]
944+ if analytic_acc_obj.search_exist(cr, uid, [('id', '=', dest_id), ('allow_all_cc', '=', True)], context=context):
945+ analytic_acc_obj.write(cr, uid, dest_id, {'allow_all_cc': False}, context=context)
946+ return res
947+
948+ def write(self, cr, uid, ids, vals, context=None):
949+ """
950+ See _check_analytic_lines
951+ """
952+ res = super(dest_cc_link, self).write(cr, uid, ids, vals, context=context)
953+ self._check_analytic_lines(cr, uid, ids, context=context)
954+ return res
955+
956+ def is_inactive_dcl(self, cr, uid, dest_id, cc_id, posting_date, context=None):
957+ """
958+ Returns True if the Dest CC Link with the dest_id and cc_id exists and that the posting_date
959+ is outside its validity date range.
960+ """
961+ if context is None:
962+ context = {}
963+ inactive_dcl = False
964+ if dest_id and cc_id and posting_date:
965+ dcl_ids = self.search(cr, uid, [('dest_id', '=', dest_id), ('cc_id', '=', cc_id)], limit=1, context=context)
966+ if dcl_ids:
967+ dcl = self.browse(cr, uid, dcl_ids[0], fields_to_fetch=['active_from', 'inactive_from'], context=context)
968+ inactive_dcl = (dcl.active_from and posting_date < dcl.active_from) or (dcl.inactive_from and posting_date >= dcl.inactive_from)
969+ return inactive_dcl
970+
971+ def on_change_cc_id(self, cr, uid, ids, cc_id):
972+ """
973+ Fills in the CC Name as soon as a CC is selected
974+ """
975+ res = {}
976+ analytic_acc_obj = self.pool.get('account.analytic.account')
977+ if cc_id:
978+ name = analytic_acc_obj.read(cr, uid, cc_id, ['name'])['name']
979+ else:
980+ name = False
981+ res['value'] = {'cc_name': name, }
982+ return res
983+
984+
985+dest_cc_link()
986+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
987
988=== added file 'bin/addons/analytic_override/dest_cc_link.xml'
989--- bin/addons/analytic_override/dest_cc_link.xml 1970-01-01 00:00:00 +0000
990+++ bin/addons/analytic_override/dest_cc_link.xml 2021-05-04 08:01:40 +0000
991@@ -0,0 +1,49 @@
992+<?xml version="1.0"?>
993+<openerp>
994+ <data>
995+ <!-- DEST CC LINK - FORM VIEW -->
996+ <record id="view_dest_cc_link_form" model="ir.ui.view">
997+ <field name="name">dest.cc.link.form</field>
998+ <field name="model">dest.cc.link</field>
999+ <field name="type">form</field>
1000+ <field name="arch" type="xml">
1001+ <form noteditable="1">
1002+ <field name="cc_id"/>
1003+ <newline/>
1004+ <field name="active_from"/>
1005+ <field name="inactive_from"/>
1006+ </form>
1007+ </field>
1008+ </record>
1009+
1010+ <!-- DEST CC LINK - TREE VIEW -->
1011+ <record id="view_dest_cc_link_tree" model="ir.ui.view">
1012+ <field name="name">dest.cc.link.tree</field>
1013+ <field name="model">dest.cc.link</field>
1014+ <field name="type">tree</field>
1015+ <field name="arch" type="xml">
1016+ <tree>
1017+ <field name="cc_id"/>
1018+ <field name="active_from"/>
1019+ <field name="inactive_from"/>
1020+ </tree>
1021+ </field>
1022+ </record>
1023+
1024+ <!-- DEST CC LINK - SEARCH VIEW -->
1025+ <record id="view_dest_cc_link_search" model="ir.ui.view">
1026+ <field name="name">dest.cc.link.search</field>
1027+ <field name="model">dest.cc.link</field>
1028+ <field name="type">search</field>
1029+ <field name="arch" type="xml">
1030+ <search>
1031+ <group>
1032+ <field name="cc_id"/>
1033+ <field name="active_from"/>
1034+ <field name="inactive_from"/>
1035+ </group>
1036+ </search>
1037+ </field>
1038+ </record>
1039+ </data>
1040+</openerp>
1041
1042=== added file 'bin/addons/analytic_override/multiple_cc_selection_wizard.py'
1043--- bin/addons/analytic_override/multiple_cc_selection_wizard.py 1970-01-01 00:00:00 +0000
1044+++ bin/addons/analytic_override/multiple_cc_selection_wizard.py 2021-05-04 08:01:40 +0000
1045@@ -0,0 +1,56 @@
1046+# -*- coding: utf-8 -*-
1047+##############################################################################
1048+#
1049+# OpenERP, Open Source Management Solution
1050+# Copyright (C) 2021 MSF, TeMPO Consulting.
1051+#
1052+# This program is free software: you can redistribute it and/or modify
1053+# it under the terms of the GNU Affero General Public License as
1054+# published by the Free Software Foundation, either version 3 of the
1055+# License, or (at your option) any later version.
1056+#
1057+# This program is distributed in the hope that it will be useful,
1058+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1059+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1060+# GNU Affero General Public License for more details.
1061+#
1062+# You should have received a copy of the GNU Affero General Public License
1063+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1064+#
1065+##############################################################################
1066+
1067+from osv import fields
1068+from osv import osv
1069+
1070+
1071+class multiple_cc_selection_wizard(osv.osv_memory):
1072+ _name = 'multiple.cc.selection.wizard'
1073+
1074+ _columns = {
1075+ 'dest_id': fields.many2one('account.analytic.account', string="Destination", required=True,
1076+ domain="[('category', '=', 'DEST'), ('type', '!=', 'view')]"),
1077+ 'cc_ids': fields.many2many('account.analytic.account', 'multiple_cc_wiz_rel', 'wizard_id', 'cost_center_id',
1078+ string="Cost Centers", domain="[('category', '=', 'OC'), ('type', '!=', 'view')]"),
1079+ }
1080+
1081+ def multiple_cc_add(self, cr, uid, ids, context=None):
1082+ """
1083+ Adds the Cost Centers selected in the wizard to the current destination
1084+ without filling in the activation and inactivation dates of the related combinations.
1085+ """
1086+ if context is None:
1087+ context = {}
1088+ if isinstance(ids, (int, long)):
1089+ ids = [ids]
1090+ dest_cc_link_obj = self.pool.get('dest.cc.link')
1091+ analytic_acc_obj = self.pool.get('account.analytic.account')
1092+ wiz = self.browse(cr, uid, ids[0], context=context)
1093+ if wiz.cc_ids:
1094+ for cc in wiz.cc_ids:
1095+ # note: this automatically unticks the box "Allow all Cost Centers" (as for a manual CC addition)
1096+ dest_cc_link_obj.create(cr, uid, {'dest_id': wiz.dest_id.id, 'cc_id': cc.id}, context=context)
1097+ return {'type': 'ir.actions.act_window_close'}
1098+
1099+
1100+multiple_cc_selection_wizard()
1101+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1102
1103=== added file 'bin/addons/analytic_override/multiple_cc_selection_wizard.xml'
1104--- bin/addons/analytic_override/multiple_cc_selection_wizard.xml 1970-01-01 00:00:00 +0000
1105+++ bin/addons/analytic_override/multiple_cc_selection_wizard.xml 2021-05-04 08:01:40 +0000
1106@@ -0,0 +1,30 @@
1107+<?xml version="1.0" encoding="utf-8"?>
1108+<openerp>
1109+ <data>
1110+
1111+ <!-- MULTIPLE CC SELECTION WIZARD - FORM VIEW -->
1112+ <record id="multiple_cc_selection_wizard_form_view" model="ir.ui.view">
1113+ <field name="name">multiple.cc.selection.wizard.form</field>
1114+ <field name="model">multiple.cc.selection.wizard</field>
1115+ <field name="type">form</field>
1116+ <field name="arch" type="xml">
1117+ <form string="Add several Cost Centers">
1118+ <label nolabel="1" colspan="6"
1119+ string="This wizard enables you to select several Cost Centers at once to be added to the Destination. The activation and inactivation dates of the related Dest / CC combinations will remain empty."/>
1120+ <field name="cc_ids" nolabel="1" colspan="6">
1121+ <tree string="Cost Centers">
1122+ <field name="code"/>
1123+ <field name="name"/>
1124+ </tree>
1125+ </field>
1126+ <group colspan="6" col="4">
1127+ <label string="" colspan="2"/>
1128+ <button icon="gtk-cancel" string="Cancel" special="cancel" colspan="1"/>
1129+ <button icon="gtk-add" string="Add" name="multiple_cc_add" type="object" colspan="1"/>
1130+ </group>
1131+ </form>
1132+ </field>
1133+ </record>
1134+
1135+ </data>
1136+</openerp>
1137
1138=== modified file 'bin/addons/financing_contract/financing_contract_account_quadruplet.py'
1139--- bin/addons/financing_contract/financing_contract_account_quadruplet.py 2020-11-20 15:52:12 +0000
1140+++ bin/addons/financing_contract/financing_contract_account_quadruplet.py 2021-05-04 08:01:40 +0000
1141@@ -89,14 +89,15 @@
1142 funding_pool_associated_destinations fpad,
1143 account_destination_link lnk,
1144 account_analytic_account dest
1145- LEFT JOIN destination_cost_center_rel dest_cc_rel ON dest_cc_rel.destination_id = dest.id
1146+ LEFT JOIN dest_cc_link ON dest_cc_link.dest_id = dest.id
1147+
1148 WHERE
1149 fpacc.funding_pool_id = fp.id AND
1150 fpacc.cost_center_id = cc.id AND
1151 lnk.id = fpad.tuple_id AND
1152 fp.id = fpad.funding_pool_id AND
1153 lnk.destination_id = dest.id AND
1154- (dest.allow_all_cc = 't' or dest_cc_rel.cost_center_id = cc.id)
1155+ (dest.allow_all_cc = 't' or dest_cc_link.cc_id = cc.id)
1156
1157 UNION
1158
1159@@ -111,7 +112,7 @@
1160 account_destination_link lnk,
1161 account_account gl_account,
1162 account_analytic_account dest
1163- LEFT JOIN destination_cost_center_rel dest_cc_rel ON dest_cc_rel.destination_id = dest.id
1164+ LEFT JOIN dest_cc_link ON dest_cc_link.dest_id = dest.id
1165 where
1166 fp.allow_all_cc_with_fp = 't' and
1167 cc.type != 'view' and
1168@@ -123,7 +124,7 @@
1169 fp_account_rel.account_id= gl_account.id and
1170 lnk.account_id = gl_account.id and
1171 lnk.destination_id = dest.id and
1172- (dest.allow_all_cc = 't' or dest_cc_rel.cost_center_id = cc.id)
1173+ (dest.allow_all_cc = 't' or dest_cc_link.cc_id = cc.id)
1174
1175 UNION
1176
1177@@ -138,7 +139,7 @@
1178 account_destination_link lnk,
1179 account_account gl_account,
1180 account_analytic_account dest
1181- LEFT JOIN destination_cost_center_rel dest_cc_rel ON dest_cc_rel.destination_id = dest.id
1182+ LEFT JOIN dest_cc_link ON dest_cc_link.dest_id = dest.id
1183 where
1184 fp.allow_all_cc_with_fp = 'f' and
1185 fpacc.funding_pool_id = fp.id and
1186@@ -148,7 +149,7 @@
1187 fp_account_rel.account_id= gl_account.id and
1188 lnk.account_id = gl_account.id and
1189 lnk.destination_id = dest.id and
1190- (dest.allow_all_cc = 't' or dest_cc_rel.cost_center_id = cc.id)
1191+ (dest.allow_all_cc = 't' or dest_cc_link.cc_id = cc.id)
1192
1193 UNION
1194
1195@@ -162,7 +163,7 @@
1196 account_target_costcenter target,
1197 account_destination_link lnk,
1198 account_analytic_account dest
1199- LEFT JOIN destination_cost_center_rel dest_cc_rel ON dest_cc_rel.destination_id = dest.id
1200+ LEFT JOIN dest_cc_link ON dest_cc_link.dest_id = dest.id
1201 where
1202 fp.allow_all_cc_with_fp = 't' and
1203 cc.type != 'view' and
1204@@ -173,7 +174,7 @@
1205 lnk.id = fpad.tuple_id and
1206 fp.id = fpad.funding_pool_id and
1207 lnk.destination_id = dest.id and
1208- (dest.allow_all_cc = 't' or dest_cc_rel.cost_center_id = cc.id)
1209+ (dest.allow_all_cc = 't' or dest_cc_link.cc_id = cc.id)
1210 ) AS combinations
1211 )""")
1212 return res
1213
1214=== modified file 'bin/addons/msf_audittrail/__openerp__.py'
1215--- bin/addons/msf_audittrail/__openerp__.py 2020-04-20 13:31:36 +0000
1216+++ bin/addons/msf_audittrail/__openerp__.py 2021-05-04 08:01:40 +0000
1217@@ -57,6 +57,7 @@
1218 'data/audittrail_account_account.yml',
1219 'data/audittrail_account_tax.yml',
1220 'data/audittrail_res_company.yml',
1221+ 'data/audittrail_dest_cc_link.yml',
1222 'audittrail_report.xml',
1223 'audittrail_invoice_data.yml',
1224 ],
1225
1226=== added file 'bin/addons/msf_audittrail/data/audittrail_dest_cc_link.yml'
1227--- bin/addons/msf_audittrail/data/audittrail_dest_cc_link.yml 1970-01-01 00:00:00 +0000
1228+++ bin/addons/msf_audittrail/data/audittrail_dest_cc_link.yml 2021-05-04 08:01:40 +0000
1229@@ -0,0 +1,39 @@
1230+-
1231+ For Dest CC Links (dest.cc.link), track the changes on the Destinations
1232+-
1233+ !python {model: audittrail.rule}: |
1234+ name = 'Dest / CC Combinations'
1235+ object_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'dest.cc.link')], context=context)
1236+ rule_id = self.search(cr, uid, [('object_id', 'in', object_ids)], context=context)
1237+ if object_ids:
1238+ # Create the rule
1239+ fields = ['cc_id', 'active_from', 'inactive_from']
1240+ fields_ids = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=', 'dest.cc.link'), ('name', 'in', fields)], context=context)
1241+ field_name = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=', 'dest.cc.link'), ('name', '=', 'cc_id')], context=context)
1242+ field_parent = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=', 'dest.cc.link'), ('name', '=', 'dest_id')], context=context)
1243+
1244+ name_id = False
1245+ parent_id = False
1246+
1247+ if field_parent:
1248+ parent_id = field_parent[0]
1249+ if field_name:
1250+ name_id = field_name[0]
1251+
1252+ vals = {
1253+ 'name': name,
1254+ 'object_id': object_ids[0],
1255+ 'log_write': True,
1256+ 'log_unlink': True,
1257+ 'log_create': True,
1258+ 'field_ids': [(6, 0, fields_ids)],
1259+ 'parent_field_id': parent_id,
1260+ 'name_get_field_id': name_id,
1261+ }
1262+
1263+ if not rule_id:
1264+ rule_id = self.create(cr, uid, vals, context=context)
1265+ elif rule_id:
1266+ self.write(cr, uid, rule_id, vals, context=context)
1267+ # Subscribe to the rule
1268+ self.subscribe(cr, uid, rule_id)
1269
1270=== modified file 'bin/addons/msf_doc_import/msf_import_export.py'
1271--- bin/addons/msf_doc_import/msf_import_export.py 2019-05-27 10:02:18 +0000
1272+++ bin/addons/msf_doc_import/msf_import_export.py 2021-05-04 08:01:40 +0000
1273@@ -32,6 +32,7 @@
1274 from tempfile import TemporaryFile
1275 from lxml import etree
1276 from lxml.etree import XMLSyntaxError
1277+from datetime import datetime
1278
1279 from msf_doc_import.wizard.abstract_wizard_import import ImportHeader
1280 from msf_doc_import.msf_import_export_conf import MODEL_DICT
1281@@ -626,6 +627,58 @@
1282 # thread.join(wait_time)
1283 return self.bg_import(cr, uid, wiz, expected_headers, rows, raise_on_error=raise_on_error, context=context)
1284
1285+ def _handle_dest_cc_dates(self, cr, uid, data, dest_cc_list, dest_cc_tuple_list, context=None):
1286+ """
1287+ Gets and checks the dest_cc_link_active_from and dest_cc_link_inactive_from dates.
1288+ Updates the dest_cc_tuple_list with tuples containing (cost_center, active_date, inactive_date)
1289+ """
1290+ if context is None:
1291+ context = {}
1292+ dest_cc_active_date_list = []
1293+ dest_cc_inactive_date_list = []
1294+ active_from = (True, 'dest_cc_link_active_from', _("Activation Combination Dest / CC from"))
1295+ inactive_from = (False, 'dest_cc_link_inactive_from', _("Inactivation Combination Dest / CC from"))
1296+ for t in [active_from, inactive_from]:
1297+ active = t[0]
1298+ col_name = t[1]
1299+ col_str = t[2]
1300+ dest_cc_date_list = []
1301+ if data.get(col_name):
1302+ split_char = ';'
1303+ if split_char not in data.get(col_name):
1304+ split_char = ','
1305+ for cost_center_date in data.get(col_name).split(split_char):
1306+ cc_date = cost_center_date.strip()
1307+ if cc_date:
1308+ cc_date = cc_date.replace(' 00:00:00.00', '') # can be if there is only one date in the cell
1309+ try:
1310+ cc_date = datetime.strptime(cc_date, "%Y-%m-%d")
1311+ except ValueError:
1312+ raise Exception(_('The dates in the column "%s" should use the format YYYY-MM-DD.') % col_str)
1313+ else:
1314+ cc_date = False # the related Dest/CC combination has no activation/inactivation date
1315+ dest_cc_date_list.append(cc_date)
1316+ del data[col_name]
1317+ if len(dest_cc_date_list) > len(dest_cc_list):
1318+ raise Exception(_('The number of dates in the column "%s" exceeds the number of Cost Centers indicated.') % col_str)
1319+ if active:
1320+ dest_cc_active_date_list = dest_cc_date_list[:]
1321+ else:
1322+ dest_cc_inactive_date_list = dest_cc_date_list[:]
1323+ for num, cc in enumerate(dest_cc_list):
1324+ try:
1325+ dest_cc_active_date = dest_cc_active_date_list[num]
1326+ except IndexError:
1327+ dest_cc_active_date = False
1328+ try:
1329+ dest_cc_inactive_date = dest_cc_inactive_date_list[num]
1330+ except IndexError:
1331+ dest_cc_inactive_date = False
1332+ if dest_cc_active_date and dest_cc_inactive_date and dest_cc_active_date >= dest_cc_inactive_date:
1333+ cc_code = self.pool.get('account.analytic.account').read(cr, uid, cc, ['code'], context=context)['code'] or ''
1334+ raise Exception(_('The activation date related to the Cost Center %s must be before the inactivation date.') % cc_code)
1335+ dest_cc_tuple_list.append((cc, dest_cc_active_date, dest_cc_inactive_date))
1336+
1337 def bg_import(self, cr, uid, import_brw, headers, rows, raise_on_error=False, context=None):
1338 """
1339 Run the import of lines in background
1340@@ -647,6 +700,7 @@
1341 acc_obj = self.pool.get('account.account')
1342 acc_analytic_obj = self.pool.get('account.analytic.account')
1343 acc_dest_obj = self.pool.get('account.destination.link')
1344+ dest_cc_link_obj = self.pool.get('dest.cc.link')
1345
1346 cost_centers_cache = {}
1347 gl_account_cache = {}
1348@@ -731,7 +785,7 @@
1349 # custom process to retrieve CC, Destination_ids
1350 custom_m2m = []
1351 if import_brw.model_list_selection == 'destinations':
1352- custom_m2m = ['dest_cc_ids', 'destination_ids']
1353+ custom_m2m = ['destination_ids']
1354 elif import_brw.model_list_selection == 'funding_pools':
1355 custom_m2m = ['cost_center_ids', 'tuple_destination_account_ids']
1356 for c_m2m in custom_m2m:
1357@@ -1025,6 +1079,7 @@
1358 data['tuple_destination_account_ids'] = [(6, 0, [])]
1359
1360 # Destinations
1361+ dest_cc_tuple_list = []
1362 if import_brw.model_list_selection == 'destinations':
1363 context['from_import_menu'] = True
1364 data['category'] = 'DEST'
1365@@ -1042,14 +1097,14 @@
1366 if data['type'] not in ['normal', 'view']:
1367 raise Exception(_('The Type must be either "Normal" or "View".'))
1368 # Cost Centers
1369- if data.get('dest_cc_ids'):
1370+ dest_cc_list = []
1371+ if data.get('dest_cc_link_ids'):
1372 if data.get('allow_all_cc'):
1373 raise Exception(_("Please either list the Cost Centers to allow, or allow all Cost Centers."))
1374- dest_cc_list = []
1375 split_char = ';'
1376- if split_char not in data.get('dest_cc_ids'):
1377+ if split_char not in data.get('dest_cc_link_ids'):
1378 split_char = ','
1379- for cost_center in data.get('dest_cc_ids').split(split_char):
1380+ for cost_center in data.get('dest_cc_link_ids').split(split_char):
1381 cc = cost_center.strip()
1382 if cc not in cost_centers_cache:
1383 cc_dom = [('category', '=', 'OC'), ('type', '=', 'normal'), ('code', '=', cc)]
1384@@ -1059,9 +1114,7 @@
1385 dest_cc_list.append(cc_ids[0])
1386 else:
1387 raise Exception(_('Cost Center "%s" not found.') % cc)
1388- data['dest_cc_ids'] = [(6, 0, dest_cc_list)]
1389- else:
1390- data['dest_cc_ids'] = [(6, 0, [])]
1391+ self._handle_dest_cc_dates(cr, uid, data, dest_cc_list, dest_cc_tuple_list, context=context)
1392 # Accounts
1393 if data.get('destination_ids'): # "destinations_ids" corresponds to G/L accounts...
1394 acc_list = []
1395@@ -1089,8 +1142,6 @@
1396 # in case of empty columns on non-required fields, existing values should be deleted
1397 if 'date' not in data:
1398 data['date'] = False
1399- if 'dest_cc_ids' not in data:
1400- data['dest_cc_ids'] = [(6, 0, [])]
1401 if 'allow_all_cc' not in data:
1402 data['allow_all_cc'] = False
1403 if 'destination_ids' not in data:
1404@@ -1164,6 +1215,7 @@
1405
1406 data.update(forced_values)
1407
1408+ id_created = False
1409 if data.get('comment') == '[DELETE]':
1410 impobj.unlink(cr, uid, ids_to_update, context=context)
1411 nb_lines_deleted += len(ids_to_update)
1412@@ -1182,11 +1234,66 @@
1413 line_created = impobj.create(cr, uid, data, context=context)
1414 lines_already_updated.append(line_created)
1415 else:
1416- impobj.create(cr, uid, data, context=context)
1417+ id_created = impobj.create(cr, uid, data, context=context)
1418 nb_succes += 1
1419 processed.append((row_index+1, line_data))
1420 if allow_partial:
1421 cr.commit()
1422+ # For Dest CC Links: create, update or delete the links if necessary
1423+ if import_brw.model_list_selection == 'destinations':
1424+ if isinstance(ids_to_update, (int, long)):
1425+ ids_to_update = [ids_to_update]
1426+ if not dest_cc_tuple_list and ids_to_update:
1427+ # UC1: Dest CC Link column empty => delete all current Dest/CC combinations attached to the Dest
1428+ old_dcl_ids = dest_cc_link_obj.search(cr, uid, [('dest_id', 'in', ids_to_update)], order='NO_ORDER', context=context)
1429+ if old_dcl_ids:
1430+ dest_cc_link_obj.unlink(cr, uid, old_dcl_ids, context=context)
1431+ else:
1432+ # UC2: new dest
1433+ if id_created:
1434+ for cc, active_date, inactive_date in dest_cc_tuple_list:
1435+ dest_cc_link_obj.create(cr, uid, {'cc_id': cc, 'dest_id': id_created,
1436+ 'active_from': active_date, 'inactive_from': inactive_date},
1437+ context=context)
1438+ elif ids_to_update:
1439+ for dest_id in ids_to_update:
1440+ dest = acc_analytic_obj.browse(cr, uid, dest_id, fields_to_fetch=['dest_cc_link_ids'], context=context)
1441+ current_cc_ids = [dest_cc_link.cc_id.id for dest_cc_link in dest.dest_cc_link_ids]
1442+ new_cc_ids = []
1443+ for cc, active_date, inactive_date in dest_cc_tuple_list:
1444+ new_cc_ids.append(cc)
1445+ # UC3: new combinations in existing Destinations
1446+ if cc not in current_cc_ids:
1447+ dest_cc_link_obj.create(cr, uid, {'cc_id': cc, 'dest_id': dest_id,
1448+ 'active_from': active_date, 'inactive_from': inactive_date},
1449+ context=context)
1450+ else:
1451+ # UC4: combinations to be updated with new dates
1452+ dcl_ids = dest_cc_link_obj.search(cr, uid,
1453+ [('dest_id', '=', dest_id), ('cc_id', '=', cc)],
1454+ limit=1, context=context)
1455+ if dcl_ids:
1456+ dest_cc_link = dest_cc_link_obj.read(cr, uid, dcl_ids[0],
1457+ ['active_from', 'inactive_from'], context=context)
1458+ if dest_cc_link['active_from']:
1459+ current_active_dt = datetime.strptime(dest_cc_link['active_from'], "%Y-%m-%d")
1460+ else:
1461+ current_active_dt = False
1462+ if dest_cc_link['inactive_from']:
1463+ current_inactive_dt = datetime.strptime(dest_cc_link['inactive_from'], "%Y-%m-%d")
1464+ else:
1465+ current_inactive_dt = False
1466+ if (current_active_dt != active_date) or (current_inactive_dt != inactive_date):
1467+ dest_cc_link_obj.write(cr, uid, dest_cc_link['id'],
1468+ {'active_from': active_date, 'inactive_from': inactive_date},
1469+ context=context)
1470+ # UC5: combinations to be deleted in existing Destinations
1471+ cc_to_be_deleted = [c for c in current_cc_ids if c not in new_cc_ids]
1472+ if cc_to_be_deleted:
1473+ dcl_to_be_deleted = dest_cc_link_obj.search(cr, uid,
1474+ [('dest_id', '=', dest_id), ('cc_id', 'in', cc_to_be_deleted)],
1475+ order='NO_ORDER', context=context)
1476+ dest_cc_link_obj.unlink(cr, uid, dcl_to_be_deleted, context=context)
1477 except (osv.except_osv, orm.except_orm) , e:
1478 logging.getLogger('import data').info('Error %s' % e.value)
1479 if raise_on_error:
1480
1481=== modified file 'bin/addons/msf_doc_import/msf_import_export_conf.py'
1482--- bin/addons/msf_doc_import/msf_import_export_conf.py 2019-05-27 09:37:55 +0000
1483+++ bin/addons/msf_doc_import/msf_import_export_conf.py 2021-05-04 08:01:40 +0000
1484@@ -549,7 +549,9 @@
1485 'type',
1486 'date_start',
1487 'date', # "inactive from"
1488- 'dest_cc_ids',
1489+ 'dest_cc_link_ids',
1490+ 'dest_cc_link_active_from',
1491+ 'dest_cc_link_inactive_from',
1492 'destination_ids',
1493 'allow_all_cc',
1494 ],
1495
1496=== modified file 'bin/addons/msf_homere_interface/hr_payroll.py'
1497--- bin/addons/msf_homere_interface/hr_payroll.py 2020-10-13 10:14:53 +0000
1498+++ bin/addons/msf_homere_interface/hr_payroll.py 2021-05-04 08:01:40 +0000
1499@@ -43,6 +43,7 @@
1500 # Prepare some values
1501 res = {}
1502 ad_obj = self.pool.get('analytic.distribution')
1503+ dest_cc_link_obj = self.pool.get('dest.cc.link')
1504 # Search MSF Private Fund element, because it's valid with all accounts
1505 try:
1506 fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1507@@ -84,6 +85,10 @@
1508 if dest and dest.filter_active is False:
1509 res[line.id] = 'invalid'
1510 continue
1511+ if line.destination_id and line.cost_center_id:
1512+ if dest_cc_link_obj.is_inactive_dcl(cr, uid, line.destination_id.id, line.cost_center_id.id, line.date, context=context):
1513+ res[line.id] = 'invalid'
1514+ continue
1515 # G Check
1516 if line.funding_pool_id:
1517 fp = self.pool.get('account.analytic.account').browse(cr, uid, line.funding_pool_id.id, context={'date': line.document_date})
1518@@ -202,6 +207,26 @@
1519 ])
1520 return to_update
1521
1522+ def _get_trigger_state_dest_cc_link(self, cr, uid, ids, context=None):
1523+ """
1524+ Returns the list of Payroll Entries for which the AD state should be re-computed
1525+ """
1526+ if context is None:
1527+ context = {}
1528+ if isinstance(ids, (int, long)):
1529+ ids = [ids]
1530+ cc_ids = []
1531+ dest_ids = []
1532+ payroll_obj = self.pool.get('hr.payroll.msf')
1533+ for dest_cc_link in self.browse(cr, uid, ids, context=context):
1534+ cc_ids.append(dest_cc_link.cc_id.id)
1535+ dest_ids.append(dest_cc_link.dest_id.id)
1536+ payroll_ids = payroll_obj.search(cr, uid, [('state', '=', 'draft'),
1537+ '|',
1538+ ('cost_center_id', 'in', cc_ids),
1539+ ('destination_id', 'in', dest_ids)], order='NO_ORDER', context=context)
1540+ return payroll_ids
1541+
1542 def _has_third_party(self, cr, uid, ids, name, arg, context=None):
1543 """
1544 Returns True if the Payroll entry is linked to either an Employee or a Supplier
1545@@ -242,12 +267,14 @@
1546 'hr.payroll.msf': (lambda self, cr, uid, ids, c=None: ids, ['account_id', 'cost_center_id', 'funding_pool_id', 'destination_id'], 10),
1547 'account.account': (_get_trigger_state_account, ['user_type_code', 'destination_ids'], 20),
1548 'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'allow_all_cc',
1549- 'dest_cc_ids', 'allow_all_cc_with_fp',
1550+ 'allow_all_cc_with_fp',
1551 'cost_center_ids', 'select_accounts_only',
1552 'fp_account_ids',
1553 'tuple_destination_account_ids'],
1554 20),
1555 'account.destination.link': (_get_trigger_state_dest_link, ['account_id', 'destination_id'], 30),
1556+ 'dest.cc.link': (_get_trigger_state_dest_cc_link,
1557+ ['cc_id', 'dest_id', 'active_from', 'inactive_from'], 40),
1558 }
1559 ),
1560 'partner_type': fields.function(_get_third_parties, type='reference', method=True, string="Third Parties", readonly=True,
1561
1562=== modified file 'bin/addons/msf_profile/data/patches.xml'
1563--- bin/addons/msf_profile/data/patches.xml 2021-02-04 16:31:58 +0000
1564+++ bin/addons/msf_profile/data/patches.xml 2021-05-04 08:01:40 +0000
1565@@ -647,5 +647,10 @@
1566 <field name="method">us_6796_hide_prod_status_inconsistencies</field>
1567 </record>
1568
1569+ <!-- UF21.0 -->
1570+ <record id="us_7295_update_new_dest_cc_link" model="patch.scripts">
1571+ <field name="method">us_7295_update_new_dest_cc_link</field>
1572+ </record>
1573+
1574 </data>
1575 </openerp>
1576
1577=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
1578--- bin/addons/msf_profile/i18n/fr_MF.po 2021-04-22 07:55:16 +0000
1579+++ bin/addons/msf_profile/i18n/fr_MF.po 2021-05-04 08:01:40 +0000
1580@@ -21113,6 +21113,7 @@
1581 #: report:kitting.order.report:0
1582 #: field:replenishment.inventory.review.line.exp,name:0
1583 #: field:replenishment.segment.line.amc.month_exp,name:0
1584+#: view:account.analytic.account:0
1585 #, python-format
1586 msgid "Name"
1587 msgstr "Nom"
1588@@ -34816,6 +34817,7 @@
1589 #: field:products.situation.report,p_code:0
1590 #: report:addons/msf_tools/report/report_stock_pipe_per_product_instance_xls.mako:86
1591 #: field:account.target.costcenter,cost_center_code:0
1592+#: view:account.analytic.account:0
1593 #, python-format
1594 msgid "Code"
1595 msgstr "Code"
1596@@ -44534,9 +44536,10 @@
1597 msgid "No donation account found for this line: %s. (product: %s)"
1598 msgstr "No donation account found for this line: %s. (product: %s)"
1599
1600-#. modules: account, base
1601+#. modules: account, base, analytic_override
1602 #: view:account.addtmpl.wizard:0
1603 #: view:res.widget.wizard:0
1604+#: view:multiple.cc.selection.wizard:0
1605 msgid "Add"
1606 msgstr "Ajouter"
1607
1608@@ -47144,7 +47147,7 @@
1609 #: code:addons/analytic_distribution/wizard/commitment_analytic_reallocation.py:140
1610 #, python-format
1611 msgid "Non compatible entries found: %s"
1612-msgstr "Non compatible entries found: %s"
1613+msgstr "Ecritures non compatibles trouvées : %s"
1614
1615 #. modules: delivery_mechanism, tender_flow, analytic_distribution_supply, product_nomenclature, return_claim, sync_client, res_currency_tables, supplier_catalogue, import_data, stock_batch_recall, stock, product, reason_types_moves, consumption_calculation, register_accounting, specific_rules, kit, base, account_period_closing_level, account_subscription, msf_cross_docking, purchase, account, msf_outgoing, resource, procurement_auto, msf_config_locations, sale, account_override, sourcing, sync_so, res_currency_functional, account_hq_entries
1616 #: code:addons/account/account.py:445
1617@@ -66089,6 +66092,9 @@
1618 #: report:addons/account/report/free_allocation_report.mako:176
1619 #: field:free.allocation.wizard,cost_center_ids:0
1620 #: field:account.analytic.account,dest_cc_ids:0
1621+#: field:account.analytic.account,dest_cc_link_ids:0
1622+#: view:multiple.cc.selection.wizard:0
1623+#: field:multiple.cc.selection.wizard,cc_ids:0
1624 msgid "Cost Centers"
1625 msgstr "Centres de Coût"
1626
1627@@ -80570,6 +80576,7 @@
1628 #: code:addons/register_accounting/wizard/wizard_register_import.py:570
1629 #: report:addons/account/report/free_allocation_report.mako:205
1630 #: code:addons/msf_doc_import/wizard/wizard_po_simulation_screen.py:606
1631+#: field:dest.cc.link,cc_id:0
1632 #, python-format
1633 msgid "Cost Center"
1634 msgstr "Centre de Coût"
1635@@ -94521,6 +94528,8 @@
1636 #: code:addons/stock_override/report/report_stock_move.py:759
1637 #: report:addons/account/report/invoice_excel_export.mako:75
1638 #: field:financing.contract.account.quadruplet,account_destination_id:0
1639+#: field:dest.cc.link,dest_id:0
1640+#: field:multiple.cc.selection.wizard,dest_id:0
1641 #, python-format
1642 msgid "Destination"
1643 msgstr "Destination"
1644@@ -95893,7 +95902,7 @@
1645 msgid "Search Uninvoiced Lines"
1646 msgstr "Rechercher Lignes non-facturées"
1647
1648-#. modules: res_currency_functional, financing_contract, msf_tools, account_hq_entries, account_override, product_attributes, base_report_designer, register_accounting, procurement_cycle, msf_accrual, finance, sync_client, purchase_followup, account_mcdb, res_currency_tables, supplier_catalogue, procurement_request, purchase_compare_rfq, board, stock_override, msf_doc_import, analytic_distribution, threshold_value, msf_homere_interface, msf_instance, account_reconciliation, consumption_calculation, purchase_override, specific_rules, kit, base, account_period_closing_level, msf_currency_revaluation, msf_supply_doc_export, product_list, procurement_report, msf_budget, account_corrections, account, msf_outgoing, stock_move_tracking, purchase_allocation_report, procurement_auto, documents_done, sale, msf_config_locations, sales_followup, vertical_integration, procurement, sourcing, purchase, msf_audittrail, tender_flow, stock, msf_profile
1649+#. modules: res_currency_functional, financing_contract, msf_tools, account_hq_entries, account_override, product_attributes, base_report_designer, register_accounting, procurement_cycle, msf_accrual, finance, sync_client, purchase_followup, account_mcdb, res_currency_tables, supplier_catalogue, procurement_request, purchase_compare_rfq, board, stock_override, msf_doc_import, analytic_distribution, threshold_value, msf_homere_interface, msf_instance, account_reconciliation, consumption_calculation, purchase_override, specific_rules, kit, base, account_period_closing_level, msf_currency_revaluation, msf_supply_doc_export, product_list, procurement_report, msf_budget, account_corrections, account, msf_outgoing, stock_move_tracking, purchase_allocation_report, procurement_auto, documents_done, sale, msf_config_locations, sales_followup, vertical_integration, procurement, sourcing, purchase, msf_audittrail, tender_flow, stock, msf_profile, analytic_override
1650 #: view:account.addtmpl.wizard:0
1651 #: view:account.aged.trial.balance:0
1652 #: view:account.analytic.Journal.report:0
1653@@ -96127,6 +96136,7 @@
1654 #: view:product.stock_out:0
1655 #: view:replenishment.parent.segment:0
1656 #: view:view.expired.expiring.stock:0
1657+#: view:multiple.cc.selection.wizard:0
1658 #, python-format
1659 msgid "Cancel"
1660 msgstr "Annuler"
1661@@ -99905,6 +99915,12 @@
1662 msgstr "%s: FP inactif (%s)"
1663
1664 #. module: account_hq_entries
1665+#: code:addons/account_hq_entries/hq_entries.py:94
1666+#, python-format
1667+msgid "%s: inactive combination (%s - %s)"
1668+msgstr "%s: combinaison inactive (%s - %s)"
1669+
1670+#. module: account_hq_entries
1671 #: field:hq.entries,is_account_partner_compatible:0
1672 msgid "Account and partner compatible ?"
1673 msgstr "Compte et partenaire compatibles ?"
1674@@ -100211,11 +100227,23 @@
1675 msgstr "Date du CC"
1676
1677 #. module: analytic_distribution
1678+#: code:addons/analytic_distribution/analytic_line.py:563
1679+#, python-format
1680+msgid "DEST/CC combination date"
1681+msgstr "Date de la combinaison DEST/CC"
1682+
1683+#. module: analytic_distribution
1684 #: code:addons/analytic_distribution/analytic_line.py:548
1685 #, python-format
1686 msgid "FP date"
1687 msgstr "Date du FP"
1688
1689+#. module: analytic_distribution
1690+#: code:addons/analytic_distribution/analytic_distribution.py:237
1691+#, python-format
1692+msgid "Inactive DEST/CC combination"
1693+msgstr "Combinaison DEST/CC inactive"
1694+
1695 #. module: msf_printed_documents
1696 #: code:addons/msf_printed_documents/report/report_reception.py:80
1697 #, python-format
1698@@ -111899,3 +111927,124 @@
1699 #, python-format
1700 msgid "You must have at least one line to create the Internal Move"
1701 msgstr "Vous devez avoir au moins une ligne pour pouvoir créer le Mouvement Interne"
1702+
1703+#. module: analytic_distribution
1704+#: view:account.analytic.account:0
1705+msgid "Remove all"
1706+msgstr "Supprimer tout"
1707+
1708+#. module: analytic_distribution
1709+#: view:account.analytic.account:0
1710+msgid "Do you really want to remove all the Cost Centers selected?"
1711+msgstr "Voulez-vous vraiment supprimer tous les Centres de Coût sélectionnés ?"
1712+
1713+#. modules: analytic_override, analytic_distribution
1714+#: view:account.analytic.account:0
1715+#: view:multiple.cc.selection.wizard:0
1716+msgid "Add several Cost Centers"
1717+msgstr "Ajouter plusieurs Centres de Coût"
1718+
1719+#. modules: msf_doc_import, analytic_override
1720+#: field:account.analytic.account,dest_cc_link_active_from:0
1721+#: field:dest.cc.link,active_from:0
1722+#: code:addons/msf_doc_import/msf_import_export.py:639
1723+#, python-format
1724+msgid "Activation Combination Dest / CC from"
1725+msgstr "Activation Combinaison Dest / CC à partir du"
1726+
1727+#. modules: msf_doc_import, analytic_override
1728+#: field:account.analytic.account,dest_cc_link_inactive_from:0
1729+#: field:dest.cc.link,inactive_from:0
1730+#: code:addons/msf_doc_import/msf_import_export.py:640
1731+#, python-format
1732+msgid "Inactivation Combination Dest / CC from"
1733+msgstr "Inactivation Combinaison Dest / CC à partir du"
1734+
1735+#. module: analytic_override
1736+#: help:account.analytic.account,dest_cc_link_active_from:0
1737+#: help:account.analytic.account,dest_cc_link_inactive_from:0
1738+msgid "Technical field used for Import Tools only"
1739+msgstr "Champ technique utilisé pour les Outils d'Import uniquement"
1740+
1741+#. module: analytic_override
1742+#: field:account.analytic.account,selected_in_dest:0
1743+msgid "Selected in Destination"
1744+msgstr "Sélectionné dans la Destination"
1745+
1746+#. module: analytic_override
1747+#: model:ir.model,name:analytic_override.model_dest_cc_link
1748+msgid "Destination / Cost Center Combination"
1749+msgstr "Combinaison Destination / Centre de Coût"
1750+
1751+#. module: analytic_override
1752+#: field:dest.cc.link,cc_code:0
1753+msgid "Cost Center Code"
1754+msgstr "Code du Centre de Coût"
1755+
1756+#. module: analytic_override
1757+#: field:dest.cc.link,cc_name:0
1758+msgid "Cost Center Name"
1759+msgstr "Nom du Centre de Coût"
1760+
1761+#. module: analytic_override
1762+#: sql_constraint:dest.cc.link:0
1763+msgid "Each Cost Center can only be added once to the same Destination."
1764+msgstr "Chaque Centre de Coût ne peut être ajouté qu'une seule fois à une même Destination."
1765+
1766+#. module: analytic_override
1767+#: sql_constraint:dest.cc.link:0
1768+msgid "The Activation date of the \"Combination Dest / CC\" must be before the Inactivation date."
1769+msgstr "La date d'Activation de la \"Combinaison Dest / CC\" doit précéder la date d'Inactivation."
1770+
1771+#. module: msf_doc_import
1772+#: code:addons/msf_doc_import/msf_import_export.py:663
1773+#, python-format
1774+msgid "The number of dates in the column \"%s\" exceeds the number of Cost Centers indicated."
1775+msgstr "Le nombre de dates dans la colonne \"%s\" dépasse le nombre de Centres de Coût indiqués."
1776+
1777+#. module: msf_doc_import
1778+#: code:addons/msf_doc_import/msf_import_export.py:679
1779+#, python-format
1780+msgid "The activation date related to the Cost Center %s must be before the inactivation date."
1781+msgstr "La date d'activation associée au Centre de Coût %s doit précéder la date d'inactivation."
1782+
1783+#. module: msf_doc_import
1784+#: code:addons/msf_doc_import/msf_import_export.py:657
1785+#, python-format
1786+msgid "The dates in the column \"%s\" should use the format YYYY-MM-DD."
1787+msgstr "Les dates dans la colonne \"%s\" doivent utiliser le format AAAA-MM-JJ."
1788+
1789+#. module: analytic_override
1790+#: view:multiple.cc.selection.wizard:0
1791+msgid "This wizard enables you to select several Cost Centers at once to be added to the Destination. The activation and inactivation dates of the related Dest / CC combinations will remain empty."
1792+msgstr "Cet assistant vous permet de sélectionner en une fois plusieurs Centres de Coût à ajouter à la Destination. Les dates d'activation et d'inactivation des Combinaisons Dest / CC associées resteront vides."
1793+
1794+#. module: analytic_override
1795+#: field:dest.cc.link,current_id:0
1796+msgid "DB Id (used by the UI)"
1797+msgstr "Id BD (utilisé par l'UI)"
1798+
1799+#. module: analytic_override
1800+#: code:addons/analytic_override/analytic_line.py:192
1801+#, python-format
1802+msgid "The combination \"%s - %s\" is not active."
1803+msgstr "La combinaison \"%s - %s\" n'est pas active."
1804+
1805+#. module: analytic_distribution
1806+#: code:addons/analytic_distribution/wizard/analytic_distribution_wizard.py:1024
1807+#: code:addons/analytic_distribution/account_commitment.py:204
1808+#, python-format
1809+msgid "The combination \"%s - %s\" is not active at this date: %s"
1810+msgstr "La combinaison \"%s - %s\" n'est pas active à cette date : %s"
1811+
1812+#. module: analytic_distribution
1813+#: code:addons/analytic_distribution/analytic_distribution.py:264
1814+#, python-format
1815+msgid "%sThe combination \"%s - %s\" is not active at this date: %s"
1816+msgstr "%sLa combinaison \"%s - %s\" n'est pas active à cette date : %s"
1817+
1818+#. module: analytic_override
1819+#: code:addons/analytic_override/dest_cc_link.py:72
1820+#, python-format
1821+msgid "At least one Analytic Journal Item using the combination \"%s - %s\" has a Posting Date outside the activation dates selected."
1822+msgstr "Au moins une Ligne d'Ecriture Analytique utilisant la combinaison \"%s - %s\" a une Date de Comptabilisation en dehors des dates d'activation sélectionnées."
1823
1824=== modified file 'bin/addons/msf_profile/msf_profile.py'
1825--- bin/addons/msf_profile/msf_profile.py 2021-02-04 16:31:58 +0000
1826+++ bin/addons/msf_profile/msf_profile.py 2021-05-04 08:01:40 +0000
1827@@ -52,6 +52,20 @@
1828 'model': lambda *a: 'patch.scripts',
1829 }
1830
1831+ # UF21.0
1832+ def us_7295_update_new_dest_cc_link(self, cr, uid, *a, **b):
1833+ """
1834+ CC Tab of the Destinations: replaces the old field "dest_cc_ids" by the new field "dest_cc_link_ids"
1835+ => recreates the links without activation/inactivation dates
1836+ """
1837+ cr.execute("""
1838+ INSERT INTO dest_cc_link(dest_id, cc_id)
1839+ SELECT destination_id, cost_center_id FROM destination_cost_center_rel
1840+ """)
1841+ cr.execute("DELETE FROM destination_cost_center_rel")
1842+ self._logger.warn('Destinations: %s Dest CC Links generated.', cr.rowcount)
1843+ return True
1844+
1845 # UF20.0
1846 def us_7866_fill_in_target_cc_code(self, cr, uid, *a, **b):
1847 """
1848
1849=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
1850--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-12-16 08:10:25 +0000
1851+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2021-05-04 08:01:40 +0000
1852@@ -34,6 +34,7 @@
1853 msf_sync_data_server.fys_state,TRUE,TRUE,TRUE,TRUE,bidirectional,Up,[],"['state', 'fy_id/id', 'instance_id/id']",HQ + MISSION,account.fiscalyear.state,,Fiscal years states,Valid,,130
1854 msf_sync_data_server.cost_center_cc_intermission,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('category' , '=' , 'OC'), ('code', '=', 'cc-intermission')]","['category', 'code', 'date', 'date_start', 'description', 'name', 'type']",OC,account.analytic.account,,CC-Intermission,Valid,,140
1855 msf_sync_data_server.destination_cc,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,"[('category' , '=' , 'DEST')]","['dest_cc_ids/id', 'allow_all_cc']",OC,account.analytic.account,,Destinations: Cost Center fields,Valid,,145
1856+msf_sync_data_server.dest_cc_link,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional-Private,"[]","['dest_id/id', 'cc_id/id', 'active_from', 'inactive_from']",OC,dest.cc.link,cc_id,Destination / Cost Center Combinations,Valid,,146
1857 msf_sync_data_server.gl_accounts_reconciliation,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['reconciliation_debit_account_id/id', 'reconciliation_credit_account_id/id']",OC,account.account,,GL Accounts Reconciliation Accounts,Valid,,150
1858 msf_sync_data_server.analytic_distribution,FALSE,TRUE,FALSE,FALSE,bidirectional,Bidirectional,[],['name'],HQ + MISSION,analytic.distribution,,Analytic Distribution,Valid,,200
1859 msf_sync_data_server.cost_center_distribution_line,TRUE,TRUE,TRUE,FALSE,bidirectional,Bidirectional,"[('partner_type','=','internal')]","['amount', 'analytic_id/id', 'currency_id/id', 'date', 'destination_id/id', 'distribution_id/id', 'name', 'percentage', 'partner_type', 'source_date']",HQ + MISSION,cost.center.distribution.line,analytic_id,Cost Center Distribution Line - Internal partner,Valid,,201
1860
1861=== modified file 'bin/addons/sync_client/update.py'
1862--- bin/addons/sync_client/update.py 2021-01-18 14:22:04 +0000
1863+++ bin/addons/sync_client/update.py 2021-05-04 08:01:40 +0000
1864@@ -58,6 +58,7 @@
1865 'account.mcdb',
1866 'wizard.template',
1867 'account.analytic.account',
1868+ 'dest.cc.link',
1869 ]
1870
1871
1872
1873=== modified file 'bin/addons/sync_common/common.py'
1874--- bin/addons/sync_common/common.py 2020-10-20 07:15:50 +0000
1875+++ bin/addons/sync_common/common.py 2021-05-04 08:01:40 +0000
1876@@ -168,6 +168,7 @@
1877 'cash.request.liquidity.total',
1878 'hr.payment.method',
1879 'wizard.template',
1880+ 'dest.cc.link',
1881 ]
1882
1883 OC_LIST = ['OCA', 'OCB', 'OCBA', 'OCG', 'OCP']
1884
1885=== modified file 'bin/addons/sync_so/specific_xml_id.py'
1886--- bin/addons/sync_so/specific_xml_id.py 2020-11-09 10:40:25 +0000
1887+++ bin/addons/sync_so/specific_xml_id.py 2021-05-04 08:01:40 +0000
1888@@ -307,6 +307,30 @@
1889
1890 account_analytic_account()
1891
1892+class dest_cc_link(osv.osv):
1893+ _inherit = 'dest.cc.link'
1894+
1895+ def get_destination_name(self, cr, uid, ids, dest_field, context=None):
1896+ '''
1897+ same destination as CC
1898+ '''
1899+ if not ids:
1900+ return []
1901+
1902+ if isinstance(ids, (long, int)):
1903+ ids = [ids]
1904+ res = dict.fromkeys(ids, False)
1905+ mapping = {}
1906+ uniq_cc_ids = {}
1907+ for dest_cc_link in self.browse(cr, uid, ids, fields_to_fetch=['cc_id'], context=context):
1908+ mapping[dest_cc_link.id] = dest_cc_link.cc_id.id
1909+ uniq_cc_ids[dest_cc_link.cc_id.id] = True
1910+ cc_destination = self.pool.get('account.analytic.account').get_destination_name(cr, uid, uniq_cc_ids.keys(), 'category', context)
1911+ for dest_cc_link_id in mapping:
1912+ res[dest_cc_link_id] = cc_destination.get(mapping[dest_cc_link_id], [])
1913+ return res
1914+
1915+dest_cc_link()
1916
1917 #US-113: Sync only to the mission with attached prop instance
1918 class financing_contract_contract(osv.osv):
1919
1920=== modified file 'bin/osv/orm.py'
1921--- bin/osv/orm.py 2021-02-02 10:20:51 +0000
1922+++ bin/osv/orm.py 2021-05-04 08:01:40 +0000
1923@@ -733,12 +733,21 @@
1924
1925 if no_data:
1926 dt = ''
1927+ rel_table_name = r[0]._table_name
1928+ name_relation = self.pool.get(rel_table_name)._rec_name
1929+ if isinstance(r[0][name_relation], browse_record):
1930+ rel = True
1931+ rel_table_name = r[0][name_relation]._table_name
1932+ all_rr = [rr[name_relation].id for rr in r]
1933+ else:
1934+ rel = False
1935+ all_rr = [rr.id for rr in r]
1936+ all_name_get = dict(self.pool.get(rel_table_name).name_get(cr, uid, all_rr, context=context))
1937 for rr in r:
1938- name_relation = self.pool.get(rr._table_name)._rec_name
1939- if isinstance(rr[name_relation], browse_record):
1940- rr = rr[name_relation]
1941- rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
1942- rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
1943+ if not rel:
1944+ rr_name = all_name_get.get(rr.id, '')
1945+ else:
1946+ rr_name = all_name_get.get(rr[name_relation].id, '')
1947 dt += tools.ustr(rr_name or '') + ','
1948 data[fpos] = dt[:-1]
1949 break

Subscribers

People subscribed via source and target branches