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

Proposed by jftempo
Status: Merged
Merged at revision: 5874
Proposed branch: lp:~julie-w/unifield-server/US-7244
Merge into: lp:unifield-server
Diff against target: 3342 lines (+1352/-694)
41 files modified
bin/addons/account/account.py (+19/-0)
bin/addons/account/account_view.xml (+51/-0)
bin/addons/account_corrections/wizard/analytic_distribution_wizard.py (+5/-0)
bin/addons/account_hq_entries/hq_entries.py (+10/-35)
bin/addons/account_hq_entries/wizard/hq_entries_split.py (+0/-30)
bin/addons/account_hq_entries/wizard/hq_reallocation.py (+6/-28)
bin/addons/account_hq_entries/wizard/wizard_view.xml (+5/-2)
bin/addons/account_mcdb/mass_reallocation_search.py (+4/-7)
bin/addons/account_override/account.py (+85/-0)
bin/addons/analytic_distribution/account.py (+6/-3)
bin/addons/analytic_distribution/account_commitment_view.xml (+1/-1)
bin/addons/analytic_distribution/account_move_line.py (+11/-2)
bin/addons/analytic_distribution/analytic_account.py (+0/-5)
bin/addons/analytic_distribution/analytic_account_view.xml (+29/-6)
bin/addons/analytic_distribution/analytic_distribution.py (+101/-26)
bin/addons/analytic_distribution/analytic_distribution_wizard_view.xml (+1/-1)
bin/addons/analytic_distribution/analytic_line.py (+25/-39)
bin/addons/analytic_distribution/report/funding_pool.py (+1/-0)
bin/addons/analytic_distribution/report/funding_pool.rml (+26/-2)
bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py (+15/-52)
bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py (+5/-26)
bin/addons/analytic_override/analytic_account.py (+213/-13)
bin/addons/financing_contract/contract.py (+9/-12)
bin/addons/financing_contract/financing_contract_account_quadruplet.py (+191/-85)
bin/addons/financing_contract/financing_contract_view.xml (+52/-9)
bin/addons/financing_contract/format.py (+5/-27)
bin/addons/financing_contract/format_line.py (+228/-81)
bin/addons/financing_contract/report/financing_contract.py (+4/-0)
bin/addons/financing_contract/report/report_project_expenses.py (+33/-54)
bin/addons/msf_audittrail/audittrail_invoice_data.yml (+1/-1)
bin/addons/msf_doc_import/account.py (+9/-21)
bin/addons/msf_homere_interface/hr.py (+16/-31)
bin/addons/msf_homere_interface/hr_payroll.py (+26/-41)
bin/addons/msf_homere_interface/hr_payroll_wizard.xml (+3/-1)
bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py (+3/-31)
bin/addons/msf_instance/account_target_costcenter.py (+2/-2)
bin/addons/msf_profile/data/patches.xml (+4/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+121/-12)
bin/addons/msf_profile/msf_profile.py (+20/-4)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+3/-3)
bin/addons/register_accounting/wizard/wizard_cash_return.py (+3/-1)
To merge this branch: bzr merge lp:~julie-w/unifield-server/US-7244
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+393179@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/account.py'
2--- bin/addons/account/account.py 2020-08-07 12:54:32 +0000
3+++ bin/addons/account/account.py 2020-11-02 12:53:09 +0000
4@@ -517,6 +517,25 @@
5 res['value'] = {'prevent_multi_curr_rec': False}
6 return res
7
8+ def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
9+ """
10+ Displays specific views when G/L accounts are selected from a Funding Pool, a Financing Contract or a Donor
11+ """
12+ if context is None:
13+ context = {}
14+ ir_model_obj = self.pool.get('ir.model.data')
15+ if context.get('from_fp') or context.get('from_grant_management'):
16+ view = False
17+ module = 'account'
18+ if view_type == 'search':
19+ search_view_name = context.get('from_grant_management') and 'view_account_contract_search' or 'view_account_fp_search'
20+ view = ir_model_obj.get_object_reference(cr, uid, module, search_view_name)
21+ elif view_type == 'tree':
22+ view = ir_model_obj.get_object_reference(cr, uid, module, 'view_account_fp_tree')
23+ if view:
24+ view_id = view[1]
25+ return super(account_account, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
26+
27
28 account_account()
29
30
31=== modified file 'bin/addons/account/account_view.xml'
32--- bin/addons/account/account_view.xml 2020-09-08 16:37:35 +0000
33+++ bin/addons/account/account_view.xml 2020-11-02 12:53:09 +0000
34@@ -239,6 +239,57 @@
35 </field>
36 </record>
37
38+ <!-- G/L account Search View to be displayed from FP -->
39+ <record id="view_account_fp_search" model="ir.ui.view">
40+ <field name="name">account.account.fp.search</field>
41+ <field name="model">account.account</field>
42+ <field name="type">search</field>
43+ <field name="priority" eval="90"/>
44+ <field name="arch" type="xml">
45+ <search>
46+ <group>
47+ <field name="code"/>
48+ <field name="name"/>
49+ </group>
50+ </search>
51+ </field>
52+ </record>
53+
54+ <!-- G/L account Search View to be displayed from contracts/donors -->
55+ <record id="view_account_contract_search" model="ir.ui.view">
56+ <field name="name">account.account.contract.search</field>
57+ <field name="model">account.account</field>
58+ <field name="type">search</field>
59+ <field name="priority" eval="91"/>
60+ <field name="arch" type="xml">
61+ <search>
62+ <group>
63+ <filter name="active" string="Active" icon="terp-check" domain="[('filter_active', '=', True)]" />
64+ <filter name="inactive" string="Inactive" icon="gtk-dialog-error" domain="[('filter_active', '=', False)]" />
65+ <field name="code"/>
66+ <field name="name"/>
67+ </group>
68+ </search>
69+ </field>
70+ </record>
71+
72+ <!-- G/L account Tree View to be displayed from FP and contracts/donors -->
73+ <record id="view_account_fp_tree" model="ir.ui.view">
74+ <field name="name">account.account.fp.tree</field>
75+ <field name="model">account.account</field>
76+ <field name="type">tree</field>
77+ <field name="priority" eval="90"/>
78+ <field name="arch" type="xml">
79+ <tree string="G/L Accounts" colors="grey:selected_in_fp or selected_in_contract"
80+ notselectable="selected_in_fp or selected_in_contract" >
81+ <field name="code"/>
82+ <field name="name"/>
83+ <field name="selected_in_fp" invisible="1"/>
84+ <field name="selected_in_contract" invisible="1"/>
85+ </tree>
86+ </field>
87+ </record>
88+
89 <record id="view_account_list" model="ir.ui.view">
90 <field name="name">account.account.list</field>
91 <field name="model">account.account</field>
92
93=== modified file 'bin/addons/account_corrections/wizard/analytic_distribution_wizard.py'
94--- bin/addons/account_corrections/wizard/analytic_distribution_wizard.py 2020-02-06 16:52:43 +0000
95+++ bin/addons/account_corrections/wizard/analytic_distribution_wizard.py 2020-11-02 12:53:09 +0000
96@@ -269,6 +269,11 @@
97 _('The Cost Center %s is not compatible with the Destination %s.') %
98 (wiz_line.cost_center_id.code or '', wiz_line.destination_id.code or ''))
99
100+ if not ad_obj.check_fp_cc_compatibility(cr, uid, wiz_line.analytic_id.id, wiz_line.cost_center_id.id, context=context):
101+ raise osv.except_osv(_('Error'),
102+ _('The Cost Center %s is not compatible with the Funding Pool %s.') %
103+ (wiz_line.cost_center_id.code or '', wiz_line.analytic_id.code or ''))
104+
105 if not wiz_line.distribution_line_id or wiz_line.distribution_line_id.id not in old_line_ids:
106 # new distribution line
107 #if self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [wiz_line.analytic_id.id]):
108
109=== modified file 'bin/addons/account_hq_entries/hq_entries.py'
110--- bin/addons/account_hq_entries/hq_entries.py 2019-03-28 13:35:11 +0000
111+++ bin/addons/account_hq_entries/hq_entries.py 2020-11-02 12:53:09 +0000
112@@ -106,7 +106,7 @@
113 continue
114 if line.analytic_id and not line.destination_id: # CASE 2/
115 # D Check, except B check
116- if line.cost_center_id.id not in [x.id for x in line.analytic_id.cost_center_ids] and line.analytic_id.id != fp_id:
117+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.analytic_id.id, line.cost_center_id.id, context=context):
118 res[line.id] = 'invalid'
119 logger.notifyChannel('account_hq_entries', netsvc.LOG_WARNING, _('%s: CC (%s) not found in FP (%s)') % (line.id or '', line.cost_center_id.code or '', line.analytic_id.code or ''))
120 continue
121@@ -119,12 +119,13 @@
122 continue
123 else: # CASE 4/
124 # C Check, except B
125- if (line.account_id.id, line.destination_id.id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in line.analytic_id.tuple_destination_account_ids if not x.disabled] and line.analytic_id.id != fp_id:
126+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, line.analytic_id.id, line.account_id.id,
127+ line.destination_id.id, context=context):
128 res[line.id] = 'invalid'
129 logger.notifyChannel('account_hq_entries', netsvc.LOG_WARNING, _('%s: Tuple Account/DEST (%s/%s) not found in FP (%s)') % (line.id or '', line.account_id.code or '', line.destination_id.code or '', line.analytic_id.code or ''))
130 continue
131 # D Check, except B check
132- if line.cost_center_id.id not in [x.id for x in line.analytic_id.cost_center_ids] and line.analytic_id.id != fp_id:
133+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.analytic_id.id, line.cost_center_id.id, context=context):
134 res[line.id] = 'invalid'
135 logger.notifyChannel('account_hq_entries', netsvc.LOG_WARNING, _('%s: CC (%s) not found in FP (%s)') % (line.id or '', line.cost_center_id.code or '', line.analytic_id.code or ''))
136 continue
137@@ -458,7 +459,7 @@
138
139 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
140 """
141- Change funding pool domain in order to include MSF Private fund
142+ Adapts domain for AD fields
143 """
144 if context is None:
145 context = {}
146@@ -466,11 +467,9 @@
147 arch = etree.fromstring(view['arch'])
148 fields = arch.xpath('field[@name="analytic_id"]')
149 if fields:
150- try:
151- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
152- except ValueError:
153- fp_id = 0
154- fields[0].set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', '&', ('cost_center_ids', '=', cost_center_id), ('tuple_destination', '=', (account_id, destination_id)), ('id', '=', %s)]" % fp_id)
155+ fields[0].set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
156+ "('fp_compatible_with_cc_ids', '=', cost_center_id), "
157+ "('fp_compatible_with_acc_dest_ids', '=', (account_id, destination_id))]")
158 # Change Destination field
159 dest_fields = arch.xpath('field[@name="destination_id"]')
160 for field in dest_fields:
161@@ -479,32 +478,8 @@
162 return view
163
164 def onchange_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False):
165- """
166- Check given funding pool with destination
167- """
168- # Prepare some values
169- res = {}
170- # If all elements given, then search FP compatibility
171- if destination_id and funding_pool_id and account_id:
172- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
173- # Search MSF Private Fund element, because it's valid with all accounts
174- try:
175- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
176- 'analytic_account_msf_private_funds')[1]
177- except ValueError:
178- fp_id = 0
179- # Delete funding_pool_id if not valid with tuple "account_id/destination_id".
180- # but do an exception for MSF Private FUND analytic account
181- if (account_id, destination_id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp_line.tuple_destination_account_ids if not x.disabled] and funding_pool_id != fp_id:
182- res = {'value': {'analytic_id': False}}
183- # If no destination, do nothing
184- elif not destination_id:
185- res = {}
186- # Otherway: delete FP
187- else:
188- res = {'value': {'analytic_id': False}}
189- # If destination given, search if given
190- return res
191+ return self.pool.get('analytic.distribution').\
192+ onchange_ad_destination(cr, uid, ids, destination_id=destination_id, funding_pool_id=funding_pool_id, account_id=account_id)
193
194 def _check_cc(self, cr, uid, ids, context=None):
195 """
196
197=== modified file 'bin/addons/account_hq_entries/wizard/hq_entries_split.py'
198--- bin/addons/account_hq_entries/wizard/hq_entries_split.py 2018-08-17 08:28:25 +0000
199+++ bin/addons/account_hq_entries/wizard/hq_entries_split.py 2020-11-02 12:53:09 +0000
200@@ -271,36 +271,6 @@
201 return super(hq_entries_split, self).create(cr, uid, vals,
202 context=context)
203
204- # UFTP-200: Add the correct funding pool domain to the split line based on the account_id and cost_center
205- def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
206- """
207- Change funding pool domain in order to include MSF Private fund
208- """
209- if context is None:
210- context = {}
211- view = super(hq_entries_split, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
212- fields = view['fields']
213- if view_type=='form' and fields:
214- if fields.get('line_ids') and fields.get('line_ids')['views']:
215- # get the default PF and include into the domain for analytic_id
216- try:
217- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
218- except ValueError:
219- fp_id = 0
220-
221- viewtemp = fields.get('line_ids')['views']
222- arch = etree.fromstring(viewtemp['tree']['arch']) # the analytic_id is found in the line_ids, one level down
223- fields = arch.xpath('field[@name="analytic_id"]')
224- if fields:
225- fields[0].set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', '&', ('cost_center_ids', '=', cost_center_id), ('tuple_destination', '=', (account_id, destination_id)), ('id', '=', %s)]" % fp_id)
226-
227- # Change Destination field
228- dest_fields = arch.xpath('field[@name="destination_id"]')
229- for field in dest_fields:
230- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'DEST'), ('destination_ids', '=', account_id)]")
231- viewtemp['tree']['arch'] = etree.tostring(arch)
232- return view
233-
234 def button_validate(self, cr, uid, ids, context=None):
235 """
236 Validate wizard lines and create new split HQ lines.
237
238=== modified file 'bin/addons/account_hq_entries/wizard/hq_reallocation.py'
239--- bin/addons/account_hq_entries/wizard/hq_reallocation.py 2019-02-06 09:25:31 +0000
240+++ bin/addons/account_hq_entries/wizard/hq_reallocation.py 2020-11-02 12:53:09 +0000
241@@ -51,7 +51,7 @@
242
243 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
244 """
245- Change funding pool domain in order to include MSF Private fund
246+ Adapts domain for AD fields
247 """
248 if not context:
249 context = {}
250@@ -68,39 +68,17 @@
251 for field in fields:
252 field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
253 # Change FP field
254- try:
255- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
256- except ValueError:
257- fp_id = 0
258 fp_fields = form.xpath('//field[@name="analytic_id"]')
259- # Do not use line with account_id, because of NO ACCOUNT_ID PRESENCE!
260+ # no restrictions are related to the G/L accounts because the wizard isn't linked to one single line with a specific account_id
261 for field in fp_fields:
262- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
263- # NO NEED TO CHANGE DESTINATION_ID FIELD because NO ACCOUNT_ID PRESENCE!
264+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
265+ "('fp_compatible_with_cc_ids', '=', cost_center_id)]")
266 view['arch'] = etree.tostring(form)
267 return view
268
269 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, analytic_id=False):
270- """
271- Check given cost_center with funding pool
272- """
273- # Prepare some values
274- res = {}
275- if cost_center_id and analytic_id:
276- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, analytic_id)
277- # Search MSF Private Fund element, because it's valid with all accounts
278- try:
279- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
280- 'analytic_account_msf_private_funds')[1]
281- except ValueError:
282- fp_id = 0
283- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and analytic_id != fp_id:
284- res = {'value': {'analytic_id': False}}
285- elif not cost_center_id:
286- res = {}
287- else:
288- res = {'value': {'analytic_id': False}}
289- return res
290+ return self.pool.get('analytic.distribution').\
291+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=analytic_id, fp_field_name='analytic_id')
292
293 def button_validate(self, cr, uid ,ids, context=None):
294 """
295
296=== modified file 'bin/addons/account_hq_entries/wizard/wizard_view.xml'
297--- bin/addons/account_hq_entries/wizard/wizard_view.xml 2019-01-08 11:20:04 +0000
298+++ bin/addons/account_hq_entries/wizard/wizard_view.xml 2020-11-02 12:53:09 +0000
299@@ -48,10 +48,13 @@
300 domain="[('restricted_area', '=', 'hq_lines_correction'), ('filter_active', '=', True)]"
301 />
302 <field name="amount"/>
303- <field name="destination_id" attrs="{'readonly': [('is_not_ad_correctable', '=', True)]}"/>
304+ <field name="destination_id" attrs="{'readonly': [('is_not_ad_correctable', '=', True)]}"
305+ domain="[('category', '=', 'DEST'), ('type', '!=', 'view'), ('destination_ids', '=', account_id)]"/>
306 <field name="cost_center_id" attrs="{'readonly': [('is_not_ad_correctable', '=', True)]}"/>
307 <field name="analytic_id" attrs="{'readonly': [('is_not_ad_correctable', '=', True)]}"
308- domain="[('type', '!=', 'view'), ('category', '=', 'FUNDING'), ('state', '=', 'open'), ('cost_center_ids', '=', cost_center_id)]"
309+ domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'),
310+ ('fp_compatible_with_cc_ids', '=', cost_center_id),
311+ ('fp_compatible_with_acc_dest_ids', '=', (account_id, destination_id))]"
312 string="Funding Pool"
313 context="{'search_default_active': 1, 'hide_inactive': 1, 'date': context.get('document_date')}"/>
314 <field name="state"/>
315
316=== modified file 'bin/addons/account_mcdb/mass_reallocation_search.py'
317--- bin/addons/account_mcdb/mass_reallocation_search.py 2013-10-04 14:54:39 +0000
318+++ bin/addons/account_mcdb/mass_reallocation_search.py 2020-11-02 12:53:09 +0000
319@@ -37,8 +37,9 @@
320 context = {}
321 if isinstance(ids, (int, long)):
322 ids = [ids]
323+ analytic_acc_obj = self.pool.get('account.analytic.account')
324 # Only process first id
325- account = self.pool.get('account.analytic.account').browse(cr, uid, ids, context=context)[0]
326+ account = analytic_acc_obj.browse(cr, uid, ids, context=context)[0]
327 if account.category != 'FUNDING':
328 raise osv.except_osv(_('Error'), _('This action only works for Funding Pool accounts!'))
329 # Take all elements to create a domain
330@@ -53,16 +54,12 @@
331 except ValueError:
332 fp_id = 0
333 if account.id != fp_id:
334- if account.tuple_destination_account_ids:
335+ if account.tuple_destination_account_ids or account.fp_account_ids:
336+ # note: this includes restrictions on Cost Centers
337 search.append(('is_fp_compat_with', '=', account.id))
338 else:
339 # trick to avoid problem with FP that have NO destination link. So we need to search a "False" Destination.
340 search.append(('destination_id', '=', 0))
341- if account.cost_center_ids:
342- search.append(('cost_center_id', 'in', [x.id for x in account.cost_center_ids]))
343- else:
344- # trick to avoid problem with FP that have NO CC.
345- search.append(('cost_center_id', '=', 0))
346 for criterium in [('account_id', '!=', account.id), ('journal_id.type', '!=', 'engagement'), ('is_reallocated', '=', False), ('is_reversal', '=', False)]:
347 search.append(criterium)
348 search.append(('contract_open','=', True))
349
350=== modified file 'bin/addons/account_override/account.py'
351--- bin/addons/account_override/account.py 2020-03-05 17:13:57 +0000
352+++ bin/addons/account_override/account.py 2020-11-02 12:53:09 +0000
353@@ -363,6 +363,83 @@
354 ret[link['account_id'][0]] = True
355 return ret
356
357+ def _get_selected_in_fp(self, cr, uid, account_ids, name=False, args=False, context=None):
358+ """
359+ Returns True for the G/L accounts already selected in the Funding Pool:
360+ they will be displayed in grey in the list and won't be re-selectable.
361+ """
362+ if context is None:
363+ context = {}
364+ if isinstance(account_ids, (int, long)):
365+ account_ids = [account_ids]
366+ selected = []
367+ acc = context.get('accounts_selected')
368+ if acc and isinstance(acc, list) and len(acc) == 1 and len(acc[0]) == 3:
369+ selected = acc[0][2]
370+ res = {}
371+ for account_id in account_ids:
372+ res[account_id] = account_id in selected
373+ return res
374+
375+ def _get_false(self, cr, uid, ids, *a, **b):
376+ """
377+ Returns False for all ids
378+ """
379+ return {}.fromkeys(ids, False)
380+
381+ def _search_selectable_in_contract(self, cr, uid, ids, field_name, arg, context=None):
382+ """
383+ Returns a domain with the G/L accounts selectable in the contract in context.
384+ The accounts must appear either in the G/L accounts or in the Account/Destination combinations linked to the
385+ Funding Pools selected in the contract.
386+ """
387+ if context is None:
388+ context = {}
389+ contract_obj = self.pool.get('financing.contract.contract')
390+ analytic_acc_obj = self.pool.get('account.analytic.account')
391+ acc_ids = set()
392+ if context.get('contract_id'):
393+ contract = contract_obj.browse(cr, uid, context['contract_id'], fields_to_fetch=['funding_pool_ids'], context=context)
394+ for contract_fp_line in contract.funding_pool_ids:
395+ acc_ids.update([t[0] for t in
396+ analytic_acc_obj.get_acc_dest_linked_to_fp(cr, uid, contract_fp_line.funding_pool_id.id, context=context)])
397+ return [('id', 'in', list(acc_ids))]
398+
399+ def _get_selected_in_contract(self, cr, uid, account_ids, name=False, args=False, context=None):
400+ """
401+ Returns True for the G/L accounts already selected in the contract or donor in context:
402+ they will be displayed in grey in the list and won't be re-selectable.
403+
404+ As soon as an account has been selected in either G/L accounts only, acc/dest combinaisons, or quadruplets,
405+ it is seen as already used.
406+ """
407+ if context is None:
408+ context = {}
409+ if isinstance(account_ids, (int, long)):
410+ account_ids = [account_ids]
411+ res = {}
412+ selected = {}
413+ current_obj = current_id = False
414+ if context.get('contract_id'):
415+ current_obj = self.pool.get('financing.contract.contract')
416+ current_id = context['contract_id']
417+ elif context.get('donor_id'):
418+ current_obj = self.pool.get('financing.contract.donor')
419+ current_id = context['donor_id']
420+ if current_obj and current_id:
421+ active_id = context.get('active_id', False)
422+ for line in current_obj.browse(cr, uid, current_id, fields_to_fetch=['actual_line_ids'], context=context).actual_line_ids:
423+ if not active_id or line.id != active_id: # skip the current reporting line
424+ for account_destination in line.account_destination_ids:
425+ selected[account_destination.account_id.id] = True
426+ for account_quadruplet in line.account_quadruplet_ids:
427+ selected[account_quadruplet.account_id.id] = True
428+ for account in line.reporting_account_ids:
429+ selected[account.id] = True
430+ for account_id in account_ids:
431+ res[account_id] = account_id in selected
432+ return res
433+
434 _columns = {
435 'name': fields.char('Name', size=128, required=True, select=True, translate=True),
436 'activation_date': fields.date('Active from', required=True),
437@@ -400,6 +477,14 @@
438 'has_partner_type_empty': fields.boolean('Empty'), # US-1307 empty
439
440 'inactivated_for_dest': fields.function(_get_inactivated_for_dest, method=True, type='boolean', string='Is inactive for destination given in context'),
441+
442+ 'selected_in_fp': fields.function(_get_selected_in_fp, string='Selected in Funding Pool', method=True, store=False, type='boolean'),
443+ # G/L acc. which CAN BE selected in the Financing Contract:
444+ 'selectable_in_contract': fields.function(_get_false, string='Selectable in Contract', method=True, store=False,
445+ type='boolean', fnct_search=_search_selectable_in_contract),
446+ # G/L acc. which ARE currently selected in the Financing Contract or Donor:
447+ 'selected_in_contract': fields.function(_get_selected_in_contract, string='Selected in Contract or Donor', method=True,
448+ store=False, type='boolean'),
449 }
450
451 _defaults = {
452
453=== modified file 'bin/addons/analytic_distribution/account.py'
454--- bin/addons/analytic_distribution/account.py 2019-05-13 09:18:37 +0000
455+++ bin/addons/analytic_distribution/account.py 2020-11-02 12:53:09 +0000
456@@ -81,8 +81,8 @@
457 return ret
458
459 _columns = {
460- 'account_id': fields.many2one('account.account', "G/L Account", required=True, domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True)]", readonly=True),
461- 'destination_id': fields.many2one('account.analytic.account', "Analytical Destination Account", required=True, domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", readonly=True),
462+ 'account_id': fields.many2one('account.account', "G/L Account", required=True, domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True)]", readonly=True, select=1),
463+ 'destination_id': fields.many2one('account.analytic.account', "Analytical Destination Account", required=True, domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", readonly=True, select=1),
464 'funding_pool_ids': fields.many2many('account.analytic.account', 'funding_pool_associated_destinations', 'tuple_id', 'funding_pool_id', "Funding Pools"),
465 'name': fields.function(_get_tuple_name, method=True, type='char', size=254, string="Name", readonly=True,
466 store={
467@@ -183,6 +183,8 @@
468 # Prepare some values
469 if not ids:
470 return True
471+ if isinstance(ids, (int, long)):
472+ ids = [ids]
473 if context is None:
474 context = {}
475 # Check default destination presence
476@@ -196,7 +198,8 @@
477 all_ids.append(dd_id)
478 super(account_account, self).write(cr, uid, [a.id], {'destination_ids': [(6, 0, all_ids)]})
479 link_obj = self.pool.get('account.destination.link')
480- link_ids = link_obj.search(cr, uid, [('account_id', 'in', ids), ('disabled', '=', True)], context=context)
481+ link_ids = link_obj.search(cr, uid, [('account_id', 'in', ids), ('destination_id', '=', dd_id), ('disabled', '=', True)],
482+ context=context)
483 if link_ids:
484 link_obj.write(cr, uid, link_ids, {'disabled': False}, context=context)
485 return res
486
487=== modified file 'bin/addons/analytic_distribution/account_commitment_view.xml'
488--- bin/addons/analytic_distribution/account_commitment_view.xml 2020-05-07 08:28:47 +0000
489+++ bin/addons/analytic_distribution/account_commitment_view.xml 2020-11-02 12:53:09 +0000
490@@ -204,8 +204,8 @@
491 <field name="arch" type="xml">
492 <form string="Intl Commitments Analytic Reallocation">
493 <group colspan="6" col="6">
494+ <field name="cost_center_id" required="0" on_change="onchange_cost_center(cost_center_id, funding_pool_id)" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
495 <field name="destination_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
496- <field name="cost_center_id" required="0" on_change="onchange_cost_center(cost_center_id, funding_pool_id)" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
497 <field name="funding_pool_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
498 </group>
499 <newline/>
500
501=== modified file 'bin/addons/analytic_distribution/account_move_line.py'
502--- bin/addons/analytic_distribution/account_move_line.py 2020-02-24 17:11:37 +0000
503+++ bin/addons/analytic_distribution/account_move_line.py 2020-11-02 12:53:09 +0000
504@@ -412,6 +412,7 @@
505 context = {}
506 if isinstance(ids, (int, long)):
507 ids = [ids]
508+ ad_obj = self.pool.get('analytic.distribution')
509 aml_duplication = '__copy_data_seen' in context and 'account.move.line' in context['__copy_data_seen'] or False
510 from_duplication = context.get('copy', False) or aml_duplication
511 if context.get('from_je_import', False) or from_duplication:
512@@ -432,7 +433,15 @@
513 vals.update({'destination_id': l.account_id.default_destination_id.id})
514 if l.employee_id.funding_pool_id:
515 vals.update({'analytic_id': l.employee_id.funding_pool_id.id})
516- if vals.get('cost_center_id') not in [cc.id for cc in l.employee_id.funding_pool_id.cost_center_ids]:
517+ use_default_pf = False
518+ if not ad_obj.check_fp_cc_compatibility(cr, uid, l.employee_id.funding_pool_id.id, l.employee_id.cost_center_id.id,
519+ context=context):
520+ use_default_pf = True
521+ elif 'destination_id' in vals and not ad_obj.check_fp_acc_dest_compatibility(cr, uid, l.employee_id.funding_pool_id.id,
522+ l.account_id.id, vals['destination_id'],
523+ context=context):
524+ use_default_pf = True
525+ if use_default_pf:
526 # Fetch default funding pool: MSF Private Fund
527 try:
528 msf_fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
529@@ -458,7 +467,7 @@
530 to_change = True
531
532 if to_change:
533- distrib_id = self.pool.get('analytic.distribution').create(cr, uid, {'name': 'check_employee_analytic_distribution'})
534+ distrib_id = ad_obj.create(cr, uid, {'name': 'check_employee_analytic_distribution'}, context=context)
535 vals.update({'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id})
536 # Create funding pool lines
537 self.pool.get('funding.pool.distribution.line').create(cr, uid, vals)
538
539=== modified file 'bin/addons/analytic_distribution/analytic_account.py'
540--- bin/addons/analytic_distribution/analytic_account.py 2017-02-10 08:51:30 +0000
541+++ bin/addons/analytic_distribution/analytic_account.py 2020-11-02 12:53:09 +0000
542@@ -38,11 +38,6 @@
543 if not default:
544 default = {}
545
546- # US-348: Reset some values when duplicating an analytic account
547- # But: duplication of funding pool should carry over the account codes (US-723)
548- account = self.browse(cr, uid, a_id, context=context)
549- if account.category != 'FUNDING':
550- default['tuple_destination_account_ids'] = []
551 default['destination_ids'] = []
552
553 # Copy analytic distribution
554
555=== modified file 'bin/addons/analytic_distribution/analytic_account_view.xml'
556--- bin/addons/analytic_distribution/analytic_account_view.xml 2020-01-30 10:18:45 +0000
557+++ bin/addons/analytic_distribution/analytic_account_view.xml 2020-11-02 12:53:09 +0000
558@@ -83,19 +83,42 @@
559 <field name="date" select="2"/>
560 </page>
561 <page string="Cost centers" attrs="{'invisible': [('category', '!=', 'FUNDING')]}">
562- <button name="button_cc_clear" type="object" string="Remove all" icon="gtk-clear" colspan="1"/>
563+ <field name="allow_all_cc_with_fp" colspan="4"
564+ on_change="on_change_allow_all_cc_with_fp(allow_all_cc_with_fp, cost_center_ids)"/>
565+ <button name="button_cc_clear" type="object" string="Remove all" icon="gtk-clear" colspan="4"/>
566 <separator/>
567- <field name="cost_center_ids" nolabel="1" domain="[('type', '!=', 'view'), ('category', '=', 'OC')]">
568+ <field name="cost_center_ids" nolabel="1" colspan="4"
569+ domain="[('type', '!=', 'view'), ('category', '=', 'OC')]"
570+ on_change="on_change_cc_with_fp(cost_center_ids)">
571 <tree string="Cost Centers" >
572 <field name="code"/>
573 <field name="name"/>
574 </tree>
575 </field>
576 </page>
577- <page string="Destinations" attrs="{'invisible': [('category', '!=', 'FUNDING')]}">
578- <button name="button_dest_clear" type="object" string="Remove all" icon="gtk-clear" colspan="1"/>
579+ <page string="Accounts/Destinations" attrs="{'invisible': [('category', '!=', 'FUNDING')]}">
580+ <field name="select_accounts_only"/>
581+ <button name="button_dest_clear" type="object" string="Remove all accounts/destinations"
582+ icon="gtk-clear" colspan="4"
583+ attrs="{'invisible': [('select_accounts_only', '=', True)]}"
584+ />
585+ <button name="button_fp_account_clear" type="object" string="Remove all accounts"
586+ icon="gtk-clear" colspan="4"
587+ attrs="{'invisible': [('select_accounts_only', '=', False)]}"
588+ />
589 <separator/>
590- <field name="tuple_destination_account_ids" nolabel="1" context="{'dest_in_use':tuple_destination_account_ids}"/>
591+ <field name="tuple_destination_account_ids" nolabel="1" colspan="4"
592+ context="{'dest_in_use': tuple_destination_account_ids}"
593+ attrs="{'invisible': [('select_accounts_only', '=', True)]}"
594+ />
595+ <field name="fp_account_ids" colspan="4" nolabel="1"
596+ context="{'from_fp': True, 'accounts_selected': fp_account_ids}"
597+ attrs="{'invisible': [('select_accounts_only', '=', False)]}">
598+ <tree string="G/L Accounts">
599+ <field name="code"/>
600+ <field name="name"/>
601+ </tree>
602+ </field>
603 </page>
604 <page string="Expense accounts" attrs="{'invisible': [('category', '!=', 'DEST')]}">
605 <field name="destination_ids" nolabel="1" domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True)]" context="{'destination_id': record_id}">
606@@ -112,7 +135,7 @@
607 <field name="allow_all_cc" colspan="4" on_change="on_change_allow_all_cc(allow_all_cc, dest_cc_ids)"/>
608 <button name="button_dest_cc_clear" type="object" string="Remove all" icon="gtk-clear" colspan="4"/>
609 <separator/>
610- <field name="dest_cc_ids" nolabel="1" colspan="4" on_change="on_change_dest_cc_ids(dest_cc_ids)">
611+ <field name="dest_cc_ids" nolabel="1" colspan="4" on_change="on_change_cc_ids(dest_cc_ids)">
612 <tree string="Cost Centers">
613 <field name="code"/>
614 <field name="name"/>
615
616=== modified file 'bin/addons/analytic_distribution/analytic_distribution.py'
617--- bin/addons/analytic_distribution/analytic_distribution.py 2020-01-30 17:08:29 +0000
618+++ bin/addons/analytic_distribution/analytic_distribution.py 2020-11-02 12:53:09 +0000
619@@ -44,6 +44,98 @@
620 return False
621 return True
622
623+ def check_fp_cc_compatibility(self, cr, uid, fp_id, cost_center_id, context=None):
624+ """
625+ Checks the compatibility between the FP and the Cost Center (cf. CC tab in the FP form).
626+ Returns False if they aren't compatible.
627+
628+ If "Allow all Cost Centers" is ticked: only CC linked to the prop. instance of the FP are allowed.
629+ """
630+ if context is None:
631+ context = {}
632+ analytic_acc_obj = self.pool.get('account.analytic.account')
633+ ir_model_data_obj = self.pool.get('ir.model.data')
634+ res = True
635+ if fp_id and cost_center_id:
636+ # The Funding Pool PF is compatible with every CC
637+ try:
638+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
639+ except ValueError:
640+ pf_id = 0
641+ if fp_id != pf_id:
642+ fp = analytic_acc_obj.browse(cr, uid, fp_id,
643+ fields_to_fetch=['category', 'allow_all_cc_with_fp', 'instance_id', 'cost_center_ids'],
644+ context=context)
645+ cc = analytic_acc_obj.browse(cr, uid, cost_center_id, fields_to_fetch=['category', 'type', 'cc_instance_ids'], context=context)
646+ if fp and cc and fp.category == 'FUNDING' and cc.category == 'OC':
647+ if fp.allow_all_cc_with_fp and cc.type != 'view' and fp.instance_id and \
648+ fp.instance_id.id in [inst.id for inst in cc.cc_instance_ids]:
649+ res = True
650+ elif cc.id in [c.id for c in fp.cost_center_ids]:
651+ res = True
652+ else:
653+ res = False
654+ return res
655+
656+ def onchange_ad_cost_center(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False, fp_field_name='funding_pool_id'):
657+ """
658+ Resets the FP in case the CC selected isn't compatible with it.
659+ """
660+ res = {}
661+ if cost_center_id and funding_pool_id and not self.check_fp_cc_compatibility(cr, uid, funding_pool_id, cost_center_id):
662+ res = {'value': {fp_field_name: False}}
663+ return res
664+
665+ def check_fp_acc_dest_compatibility(self, cr, uid, fp_id, account_id, dest_id, context=None):
666+ """
667+ Checks the compatibility between the FP and the "G/L Account/Destination" combination.
668+ Returns False if they aren't compatible.
669+ """
670+ if context is None:
671+ context = {}
672+ analytic_acc_obj = self.pool.get('account.analytic.account')
673+ account_obj = self.pool.get('account.account')
674+ ir_model_data_obj = self.pool.get('ir.model.data')
675+ res = True
676+ if fp_id and account_id and dest_id:
677+ # The Funding Pool PF is compatible with every combination
678+ try:
679+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
680+ except ValueError:
681+ pf_id = 0
682+ if fp_id != pf_id:
683+ fp = analytic_acc_obj.browse(cr, uid, fp_id,
684+ fields_to_fetch=['category', 'select_accounts_only', 'fp_account_ids',
685+ 'tuple_destination_account_ids'],
686+ context=context)
687+ if fp and fp.category == 'FUNDING':
688+ # continue only if the account and destination selected are compatible with one another
689+ account_selected = account_obj.browse(cr, uid, account_id, fields_to_fetch=['destination_ids'], context=context)
690+ if dest_id not in [d.id for d in account_selected.destination_ids]:
691+ res = False
692+ else:
693+ # when the link is made to G/L accounts only: all Destinations compatible with the acc. are allowed
694+ if fp.select_accounts_only and account_id in [a.id for a in fp.fp_account_ids]:
695+ res = True
696+ # otherwise the combination "account + dest" must be checked
697+ elif not fp.select_accounts_only and (account_id, dest_id) in \
698+ [(t.account_id.id, t.destination_id.id) for t in fp.tuple_destination_account_ids if not t.disabled]:
699+ res = True
700+ else:
701+ res = False
702+ return res
703+
704+ def onchange_ad_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False,
705+ fp_field_name='funding_pool_id'):
706+ """
707+ Resets the FP in case the Dest/Acc combination selected isn't compatible with it.
708+ """
709+ res = {}
710+ if destination_id and funding_pool_id and account_id and \
711+ not self.check_fp_acc_dest_compatibility(cr, uid, funding_pool_id, account_id, destination_id):
712+ res = {'value': {fp_field_name: False}}
713+ return res
714+
715 def _get_distribution_state(self, cr, uid, distrib_id, parent_id, account_id, context=None,
716 doc_date=False, posting_date=False, manual=False, amount=False):
717 """
718@@ -70,12 +162,6 @@
719 if amount is not None and amount is not False and abs(amount) <= 1:
720 if not all(len(d) <= 1 for d in [distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]):
721 return 'invalid_small_amount'
722- # Search MSF Private Fund element, because it's valid with all accounts
723- try:
724- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
725- 'analytic_account_msf_private_funds')[1]
726- except ValueError:
727- fp_id = 0
728 account = self.pool.get('account.account').read(cr, uid, account_id, ['destination_ids'])
729 # Check Cost Center lines regarding destination/account and destination/CC links
730 for cc_line in distrib.cost_center_lines:
731@@ -105,12 +191,10 @@
732 return 'invalid'
733 if not fp_line.analytic_id:
734 return 'invalid'
735- # If fp_line is MSF Private Fund, all is ok
736- if fp_line.analytic_id.id == fp_id:
737- continue
738- if (account_id, fp_line.destination_id.id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp_line.analytic_id.tuple_destination_account_ids if not x.disabled]:
739+ if not self.check_fp_acc_dest_compatibility(cr, uid, fp_line.analytic_id.id, account_id,
740+ fp_line.destination_id.id, context=context):
741 return 'invalid'
742- if fp_line.cost_center_id.id not in [x.id for x in fp_line.analytic_id.cost_center_ids]:
743+ if not self.check_fp_cc_compatibility(cr, uid, fp_line.analytic_id.id, fp_line.cost_center_id.id, context=context):
744 return 'invalid'
745 # Check the date validity of the free accounts used in manual entries
746 if manual and doc_date:
747@@ -135,14 +219,6 @@
748 info = ''
749 ana_obj = self.pool.get('account.analytic.account')
750 account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
751- fp = ana_obj.browse(cr, uid, analytic_id, context=context)
752- try:
753- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
754- except ValueError:
755- fp_id = 0
756- is_private_fund = False
757- if analytic_id == fp_id:
758- is_private_fund = True
759 # DISTRIBUTION VERIFICATION
760 # Check that destination is compatible with account
761 if destination_id not in [x.id for x in account.destination_ids]:
762@@ -150,13 +226,12 @@
763 # Check that Destination and Cost Center are compatible
764 if not self.check_dest_cc_compatibility(cr, uid, destination_id, cost_center_id, context=context):
765 return 'invalid', _('Cost Center not compatible with destination')
766- if not is_private_fund:
767- # Check that cost center is compatible with FP (except if FP is MSF Private Fund)
768- if cost_center_id not in [x.id for x in fp.cost_center_ids]:
769- return 'invalid', _('Cost Center not compatible with FP')
770- # Check that tuple account/destination is compatible with FP (except if FP is MSF Private Fund):
771- if (account_id, destination_id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp.tuple_destination_account_ids if not x.disabled]:
772- return 'invalid', _('account/destination tuple not compatible with given FP analytic account')
773+ # Check that cost center is compatible with FP
774+ if not self.check_fp_cc_compatibility(cr, uid, analytic_id, cost_center_id, context=context):
775+ return 'invalid', _('Cost Center not compatible with FP')
776+ # Check that tuple account/destination is compatible with FP
777+ if not self.check_fp_acc_dest_compatibility(cr, uid, analytic_id, account_id, destination_id, context=context):
778+ return 'invalid', _('account/destination tuple not compatible with given FP analytic account')
779 return res, info
780
781 def check_cc_distrib_active(self, cr, uid, distrib_br, posting_date=False, prefix='', from_supply=False):
782
783=== modified file 'bin/addons/analytic_distribution/analytic_distribution_wizard_view.xml'
784--- bin/addons/analytic_distribution/analytic_distribution_wizard_view.xml 2020-02-10 14:51:45 +0000
785+++ bin/addons/analytic_distribution/analytic_distribution_wizard_view.xml 2020-11-02 12:53:09 +0000
786@@ -32,7 +32,7 @@
787 <field name="destination_id" on_change="onchange_destination(destination_id, analytic_id, parent.account_id)"
788 context="{'search_default_active': 1, 'hide_inactive': 1, 'date': context.get('posting_date')}"/>
789 <field name="analytic_id"
790- domain="[('type', '!=', 'view'), ('category', '=', 'FUNDING'), ('state', '=', 'open'), ('cost_center_ids', '=', cost_center_id)]"
791+ domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('fp_compatible_with_cc_ids', '=', cost_center_id)]"
792 string="Funding Pool" context="{'search_default_active': 1, 'hide_inactive': 1, 'date': context.get('document_date')}"/>
793 <field name="percentage" sum="Total Percentage" digits="(16,2)"/>
794 <field name="amount" sum="Total Amount"/>
795
796=== modified file 'bin/addons/analytic_distribution/analytic_line.py'
797--- bin/addons/analytic_distribution/analytic_line.py 2019-12-12 14:07:37 +0000
798+++ bin/addons/analytic_distribution/analytic_line.py 2020-11-02 12:53:09 +0000
799@@ -46,6 +46,7 @@
800 if not args:
801 return []
802 res = []
803+ analytic_acc_obj = self.pool.get('account.analytic.account')
804 # We just support '=' operator
805 for arg in args:
806 if not arg[1]:
807@@ -54,9 +55,9 @@
808 raise osv.except_osv(_('Warning'), _('This filter is not implemented yet!'))
809 if not arg[2]:
810 raise osv.except_osv(_('Warning'), _('Some search args are missing!'))
811- analytic_account = self.pool.get('account.analytic.account').browse(cr, uid, arg[2])
812- tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in analytic_account.tuple_destination_account_ids if not x.disabled]
813- cost_center_ids = [x and x.id for x in analytic_account.cost_center_ids]
814+ fp_id = arg[2]
815+ tuple_list = analytic_acc_obj.get_acc_dest_linked_to_fp(cr, uid, fp_id, context=context)
816+ cost_center_ids = [c.id for c in analytic_acc_obj.get_cc_linked_to_fp(cr, uid, fp_id, context=context)]
817 for cc in cost_center_ids:
818 for t in tuple_list:
819 if res:
820@@ -417,11 +418,6 @@
821 res = []
822 if not account_type:
823 return res
824- try:
825- msf_private_fund = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
826- 'analytic_account_msf_private_funds')[1]
827- except ValueError:
828- msf_private_fund = 0
829 expired_date_ids = []
830 date_start = account and account.get('date_start', False) or False
831 date_stop = account and account.get('date', False) or False
832@@ -455,16 +451,9 @@
833 continue
834 if ad_obj.check_dest_cc_compatibility(cr, uid, aline.destination_id and aline.destination_id.id or False,
835 account_id, context=context):
836- if aline.account_id and aline.account_id.id == msf_private_fund:
837+ if ad_obj.check_fp_cc_compatibility(cr, uid, aline.account_id.id, account_id, context=context):
838 res.append(aline.id)
839- elif aline.account_id and aline.cost_center_id and aline.account_id.cost_center_ids:
840- if account_id in [x and x.id for x in aline.account_id.cost_center_ids] or aline.account_id.id == msf_private_fund:
841- res.append(aline.id)
842 elif account_type == 'FUNDING':
843- fp = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['cost_center_ids', 'tuple_destination_account_ids'], context=context)
844- cc_ids = fp and fp.get('cost_center_ids', []) or []
845- tuple_destination_account_ids = fp and fp.get('tuple_destination_account_ids', []) or []
846- tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in self.pool.get('account.destination.link').browse(cr, uid, tuple_destination_account_ids) if not x.disabled]
847 # Browse all analytic line to verify them
848 for aline in self.browse(cr, uid, ids):
849 # Verify that:
850@@ -472,21 +461,24 @@
851 check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
852 if check_accounts and aline.account_id.id in check_accounts:
853 continue
854- # No verification if account is MSF Private Fund because of its compatibility with all elements.
855- if account_id == msf_private_fund:
856- res.append(aline.id)
857- continue
858 # Verify that:
859 # - the line have a cost_center_id field (we expect it's a line with a funding pool account)
860 # - the cost_center is in compatible cost center from the new funding pool
861 # - the general account is in compatible account/destination tuple
862 # - the destination is in compatible account/destination tuple
863- if aline.cost_center_id and aline.cost_center_id.id in cc_ids and aline.general_account_id and aline.destination_id and (aline.general_account_id.id, aline.destination_id.id) in tuple_list:
864+ if aline.cost_center_id and aline.destination_id and \
865+ ad_obj.check_fp_cc_compatibility(cr, uid, account_id, aline.cost_center_id.id, context=context) and \
866+ ad_obj.check_fp_acc_dest_compatibility(cr, uid, account_id, aline.general_account_id.id,
867+ aline.destination_id.id, context=context):
868 res.append(aline.id)
869 elif account_type == "DEST":
870 for aline in self.browse(cr, uid, ids, context=context):
871- if ad_obj.check_dest_cc_compatibility(cr, uid, account_id, aline.cost_center_id and aline.cost_center_id.id or False, context=context) and \
872- aline.general_account_id and account_id in [x.id for x in aline.general_account_id.destination_ids]:
873+ # the following check is included into check_fp_acc_dest_compatibility:
874+ # account_id in [x.id for x in aline.general_account_id.destination_ids]
875+ if ad_obj.check_dest_cc_compatibility(cr, uid, account_id, aline.cost_center_id and aline.cost_center_id.id or False,
876+ context=context) and \
877+ ad_obj.check_fp_acc_dest_compatibility(cr, uid, aline.account_id.id, aline.general_account_id.id,
878+ account_id, context=context):
879 res.append(aline.id)
880 else:
881 # Case of FREE1 and FREE2 lines
882@@ -538,23 +530,17 @@
883 res.append((id, entry_sequence, _('CC/DEST')))
884 return False
885
886- # check funding pool (expect for MSF Private Fund)
887- if not new_fp_id == msf_pf_id: # all OK for MSF Private Fund
888- # - cost center and funding pool compatibility
889- cc_ids = [cc.id for cc in new_fp_br.cost_center_ids]
890- if not new_cc_id in cc_ids:
891- # not compatible with CC
892- res.append((id, entry_sequence, _('CC')))
893- return False
894+ # - cost center and funding pool compatibility
895+ if not ad_obj.check_fp_cc_compatibility(cr, uid, new_fp_id, new_cc_id, context=context):
896+ # not compatible with CC
897+ res.append((id, entry_sequence, _('CC')))
898+ return False
899
900- # - destination / account
901- acc_dest = (general_account_br.id, new_dest_id)
902- if acc_dest not in [x.account_id and x.destination_id and \
903- (x.account_id.id, x.destination_id.id) \
904- for x in new_fp_br.tuple_destination_account_ids if not x.disabled]:
905- # not compatible with dest/account
906- res.append((id, entry_sequence, _('account/dest')))
907- return False
908+ # - destination / account
909+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, new_fp_id, general_account_br.id, new_dest_id, context=context):
910+ # not compatible with account/dest
911+ res.append((id, entry_sequence, _('account/dest')))
912+ return False
913
914 # check active date
915 if not check_date(new_dest_br, posting_date):
916
917=== modified file 'bin/addons/analytic_distribution/report/funding_pool.py'
918--- bin/addons/analytic_distribution/report/funding_pool.py 2019-04-03 13:47:21 +0000
919+++ bin/addons/analytic_distribution/report/funding_pool.py 2020-11-02 12:53:09 +0000
920@@ -29,6 +29,7 @@
921 self.localcontext.update({
922 'locale': locale,
923 'today': self.today,
924+ 'all_cc': lambda f: self.pool.get('account.analytic.account').get_cc_linked_to_fp(cr, uid, f, context=context),
925 })
926
927 def today(self):
928
929=== modified file 'bin/addons/analytic_distribution/report/funding_pool.rml'
930--- bin/addons/analytic_distribution/report/funding_pool.rml 2019-04-17 10:00:14 +0000
931+++ bin/addons/analytic_distribution/report/funding_pool.rml 2020-11-02 12:53:09 +0000
932@@ -206,7 +206,7 @@
933 </td>
934 </tr>
935 <tr>
936- <para style="P17">[[repeatIn(o.cost_center_ids,'line')]]</para>
937+ <para style="P17">[[repeatIn(all_cc(o.id), 'line')]]</para>
938 <td>
939 <para style="P7">[[ line.code or '' ]]</para>
940 </td>
941@@ -222,9 +222,11 @@
942 <para style="P8">
943 <font color="white"> </font>
944 </para>
945- <para style="P6">Account/Destination:</para>
946+ <para style="P6">[[ o.select_accounts_only and translate("G/L Accounts:") or translate("Account/Destination:") ]]</para>
947
948+ <!-- Account/Destination -->
949 <blockTable repeatRows="1" style="Table4" colWidths="95.0,165.0,95.0,165.0">
950+ [[ not o.select_accounts_only or removeParentNode('blockTable') ]]
951 <tr>
952 <td>
953 <para style="P8">Account code</para>
954@@ -257,6 +259,28 @@
955 </tr>
956 </blockTable>
957
958+ <!-- G/L Accounts -->
959+ <blockTable repeatRows="1" style="Table4" colWidths="260.0,260.0">
960+ [[ o.select_accounts_only or removeParentNode('blockTable') ]]
961+ <tr>
962+ <td>
963+ <para style="P8">Account code</para>
964+ </td>
965+ <td>
966+ <para style="P8">Account name</para>
967+ </td>
968+ </tr>
969+ <tr>
970+ [[ repeatIn(o.fp_account_ids, 'acc') ]]
971+ <td>
972+ <para style="P7">[[ acc.code ]]</para>
973+ </td>
974+ <td>
975+ <para style="P7">[[ acc.name ]]</para>
976+ </td>
977+ </tr>
978+ </blockTable>
979+
980 <para style="P5">
981 <font color="white"> </font>
982 </para>
983
984=== modified file 'bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py'
985--- bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2020-07-16 15:42:42 +0000
986+++ bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2020-11-02 12:53:09 +0000
987@@ -237,12 +237,17 @@
988 elif (context.get('from_invoice', False) and isinstance(context.get('from_invoice'), int)) or (context.get('from_commitment', False) and isinstance(context.get('from_commitment'), int)) \
989 or (context.get('from_model', False) and isinstance(context.get('from_model'), int)) \
990 or (context.get('from_move', False) and isinstance(context.get('from_move'), int)) \
991- or (context.get('from_cash_return', False) and isinstance(context.get('from_cash_return'), int)):
992- # Filter is only on cost_center and MSF Private Fund on invoice header
993- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), ('hide_closed_fp', '=', True), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
994+ or (context.get('from_cash_return', False) and isinstance(context.get('from_cash_return'), int))\
995+ or (context.get('direct_invoice_id', False) and isinstance(context.get('direct_invoice_id'), int)):
996+ # Filter is only on cost_centers on invoice header
997+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
998+ "('hide_closed_fp', '=', True), ('fp_compatible_with_cc_ids', '=', cost_center_id)]")
999 else:
1000 # Add account_id constraints for invoice lines
1001- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), ('hide_closed_fp', '=', True), '|', '&', ('cost_center_ids', '=', cost_center_id), ('tuple_destination', '=', (parent.account_id, destination_id)), ('id', '=', %s)]" % fp_id)
1002+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
1003+ "('hide_closed_fp', '=', True), "
1004+ "('fp_compatible_with_cc_ids', '=', cost_center_id), "
1005+ "('fp_compatible_with_acc_dest_ids', '=', (parent.account_id, destination_id))]")
1006 # Change Destination field
1007 dest_fields = tree.xpath('/tree/field[@name="destination_id"]')
1008 for field in dest_fields:
1009@@ -404,60 +409,18 @@
1010 }
1011
1012 def onchange_destination(self, cr, uid, ids, destination_id=False, analytic_id=False, account_id=False):
1013- """
1014- Check given funding pool with destination
1015- """
1016- # Prepare some values
1017- res = {}
1018- # Search MSF Private Fund element, because it's valid with all accounts
1019- try:
1020- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1021- 'analytic_account_msf_private_funds')[1]
1022- except ValueError:
1023- fp_id = 0
1024-
1025- # If all elements given, then search FP compatibility
1026- if destination_id and analytic_id and account_id:
1027- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, analytic_id)
1028- # Delete analytic_id if not valid with tuple "account_id/destination_id".
1029- # but do an exception for MSF Private FUND analytic account
1030- if (account_id, destination_id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp_line.tuple_destination_account_ids if not x.disabled] and analytic_id != fp_id:
1031- res = {'value': {'analytic_id': False}}
1032- # If no destination, do nothing
1033- elif not destination_id \
1034- or analytic_id == fp_id: # PF always compatible
1035- res = {}
1036- # Otherway: delete FP
1037- else:
1038- res = {'value': {'analytic_id': False}}
1039- return res
1040+ return self.pool.get('analytic.distribution').onchange_ad_destination(cr, uid, ids, destination_id=destination_id,
1041+ funding_pool_id=analytic_id, account_id=account_id,
1042+ fp_field_name='analytic_id')
1043
1044 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, analytic_id=False):
1045- """
1046- Check given cost_center with funding pool
1047- """
1048- # Prepare some values
1049- res = {}
1050- # Search MSF Private Fund element, because it's valid with all accounts
1051- try:
1052- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1053- 'analytic_account_msf_private_funds')[1]
1054- except ValueError:
1055- fp_id = 0
1056+ return self.pool.get('analytic.distribution').\
1057+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=analytic_id, fp_field_name='analytic_id')
1058
1059- if cost_center_id and analytic_id:
1060- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, analytic_id)
1061- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and analytic_id != fp_id:
1062- res = {'value': {'analytic_id': False}}
1063- elif not cost_center_id \
1064- or analytic_id == fp_id: # PF always compatible:
1065- res = {}
1066- else:
1067- res = {'value': {'analytic_id': False}}
1068- return res
1069
1070 analytic_distribution_wizard_fp_lines()
1071
1072+
1073 class analytic_distribution_wizard_f1_lines(osv.osv_memory):
1074 _name = 'analytic.distribution.wizard.f1.lines'
1075 _description = 'analytic.distribution.wizard.lines'
1076
1077=== modified file 'bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py'
1078--- bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py 2015-02-09 12:53:11 +0000
1079+++ bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py 2020-11-02 12:53:09 +0000
1080@@ -38,7 +38,7 @@
1081
1082 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1083 """
1084- Change funding pool domain in order to include MSF Private fund
1085+ Adapts domain for AD fields
1086 """
1087 if context is None:
1088 context = {}
1089@@ -55,40 +55,19 @@
1090 for field in fields:
1091 field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
1092 # Change FP field
1093- try:
1094- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1095- except ValueError:
1096- fp_id = 0
1097 fp_fields = form.xpath('//field[@name="funding_pool_id"]')
1098 # Do not use line with account_id, because of NO ACCOUNT_ID PRESENCE!
1099 for field in fp_fields:
1100- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
1101+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
1102+ "('fp_compatible_with_cc_ids', '=', cost_center_id)]")
1103 # NO NEED TO CHANGE DESTINATION_ID FIELD because NO ACCOUNT_ID PRESENCE!
1104 # Apply changes
1105 view['arch'] = etree.tostring(form)
1106 return view
1107
1108 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
1109- """
1110- Check given cost_center with funding pool
1111- """
1112- # Prepare some values
1113- res = {}
1114- if cost_center_id and funding_pool_id:
1115- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
1116- # Search MSF Private Fund element, because it's valid with all accounts
1117- try:
1118- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1119- 'analytic_account_msf_private_funds')[1]
1120- except ValueError:
1121- fp_id = 0
1122- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and funding_pool_id != fp_id:
1123- res = {'value': {'funding_pool_id': False}}
1124- elif not cost_center_id:
1125- res = {}
1126- else:
1127- res = {'value': {'funding_pool_id': False}}
1128- return res
1129+ return self.pool.get('analytic.distribution').\
1130+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
1131
1132 def button_validate(self, cr, uid ,ids, context=None):
1133 """
1134
1135=== modified file 'bin/addons/analytic_override/analytic_account.py'
1136--- bin/addons/analytic_override/analytic_account.py 2020-10-09 14:34:01 +0000
1137+++ bin/addons/analytic_override/analytic_account.py 2020-11-02 12:53:09 +0000
1138@@ -290,6 +290,108 @@
1139 dom.append(('id', 'in', compatible_dest_ids))
1140 return dom
1141
1142+ def _search_fp_compatible_with_cc_ids(self, cr, uid, obj, name, args, context=None):
1143+ """
1144+ Returns a domain with all funding pools compatible with the selected Cost Center
1145+ E.g.: to get the FPs compatible with the CC 2, use the dom [('fp_compatible_with_cc_ids', '=', 2)]
1146+ """
1147+ dom = []
1148+ if context is None:
1149+ context = {}
1150+ ir_model_data_obj = self.pool.get('ir.model.data')
1151+ for arg in args:
1152+ if arg[0] == 'fp_compatible_with_cc_ids':
1153+ operator = arg[1]
1154+ cc_id = arg[2]
1155+ if operator != '=':
1156+ raise osv.except_osv(_('Error'), _('Filter not implemented on Funding Pools.'))
1157+ cc = False
1158+ if cc_id and isinstance(cc_id, (int, long)):
1159+ cc = self.browse(cr, uid, cc_id, fields_to_fetch=['category', 'type', 'cc_instance_ids'], context=context)
1160+ if cc.category != 'OC' or cc.type == 'view':
1161+ raise osv.except_osv(_('Error'), _('Filter only compatible with a normal-type Cost Center.'))
1162+ compatible_fp_ids = []
1163+ # The Funding Pool PF is compatible with every CC
1164+ try:
1165+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1166+ except ValueError:
1167+ pf_id = 0
1168+ compatible_fp_ids.append(pf_id)
1169+ if cc:
1170+ other_fp_ids = self.search(cr, uid, [('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('id', '!=', pf_id)],
1171+ context=context)
1172+ cc_inst_ids = [inst.id for inst in cc.cc_instance_ids]
1173+ for fp in self.browse(cr, uid, other_fp_ids,
1174+ fields_to_fetch=['allow_all_cc_with_fp', 'instance_id', 'cost_center_ids'],
1175+ context=context):
1176+ if fp.allow_all_cc_with_fp and fp.instance_id and fp.instance_id.id in cc_inst_ids:
1177+ compatible = True
1178+ elif cc.id in [c.id for c in fp.cost_center_ids]:
1179+ compatible = True
1180+ else:
1181+ compatible = False
1182+ if compatible:
1183+ compatible_fp_ids.append(fp.id)
1184+ dom.append(('id', 'in', compatible_fp_ids))
1185+ return dom
1186+
1187+ def _search_fp_compatible_with_acc_dest_ids(self, cr, uid, obj, name, args, context=None):
1188+ """
1189+ Returns a domain with all funding pools compatible with the selected Account/Destination combination.
1190+ It requires a tuple with (account, destination), e.g.: to get the FPs compatible with the G/L account 20 and the
1191+ destination 30, use the dom [('fp_compatible_with_acc_dest_ids', '=', (20, 30))]
1192+ """
1193+ fp_ids = []
1194+ if context is None:
1195+ context = {}
1196+ ir_model_data_obj = self.pool.get('ir.model.data')
1197+ account_obj = self.pool.get('account.account')
1198+ for arg in args:
1199+ if arg[0] == 'fp_compatible_with_acc_dest_ids':
1200+ operator = arg[1]
1201+ if operator != '=':
1202+ raise osv.except_osv(_('Error'), _('Filter not implemented on Funding Pools.'))
1203+ acc_dest = arg[2]
1204+ acc_id = dest_id = False
1205+ if acc_dest and isinstance(acc_dest, tuple) and len(acc_dest) == 2:
1206+ acc_id = acc_dest[0]
1207+ dest_id = acc_dest[1]
1208+ # The Funding Pool PF is compatible with everything and must always be displayed
1209+ try:
1210+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1211+ except ValueError:
1212+ pf_id = 0
1213+ fp_ids.append(pf_id)
1214+ if acc_id and dest_id:
1215+ account_selected = account_obj.browse(cr, uid, acc_id, fields_to_fetch=['destination_ids'], context=context)
1216+ # search for compatible FPs only if the account and destination selected are compatible with one another
1217+ if dest_id in [d.id for d in account_selected.destination_ids]:
1218+ # note: when the link is made to G/L accounts only, all Destinations compatible with the acc. are allowed
1219+ cr.execute('''
1220+ SELECT fp.id
1221+ FROM
1222+ account_analytic_account fp
1223+ LEFT JOIN fp_account_rel ON fp_account_rel.fp_id = fp.id
1224+ LEFT JOIN funding_pool_associated_destinations ON funding_pool_associated_destinations.funding_pool_id = fp.id
1225+ LEFT JOIN account_destination_link link ON link.id = funding_pool_associated_destinations.tuple_id
1226+ WHERE
1227+ fp.category = 'FUNDING' AND
1228+ fp.type != 'view' AND
1229+ fp.id != %(pf_id)s AND
1230+ (
1231+ fp.select_accounts_only = 't' AND
1232+ fp_account_rel.account_id = %(acc_id)s
1233+ OR
1234+ fp.select_accounts_only = 'f' AND
1235+ link.account_id = %(acc_id)s AND
1236+ link.destination_id = %(dest_id)s AND
1237+ link.disabled = 'f'
1238+ )
1239+ ''', {'pf_id': pf_id, 'acc_id': acc_id, 'dest_id': dest_id})
1240+ other_fp_ids = [x[0] for x in cr.fetchall()]
1241+ fp_ids.extend(other_fp_ids)
1242+ return [('id', 'in', fp_ids)]
1243+
1244 def _get_cc_instance_ids(self, cr, uid, ids, fields, arg, context=None):
1245 """
1246 Computes the values for fields.function fields, retrieving:
1247@@ -299,7 +401,8 @@
1248 ...as Cost centre picked for PO/FO reference => po_fo_cc_instance_ids
1249 (Note that those fields should theoretically always be linked to one single instance,
1250 but they are set as one2many in order to be consistent with the type of fields used in the related object.)
1251- - the Missions where the Cost Center is added to => cc_missions
1252+ - the Instances where the Cost Center is added to => cc_instance_ids
1253+ - the related Missions => cc_missions
1254 """
1255 if context is None:
1256 context = {}
1257@@ -311,6 +414,7 @@
1258 top_instance_ids = []
1259 target_instance_ids = []
1260 po_fo_instance_ids = []
1261+ all_instance_ids = []
1262 missions = set()
1263 missions_str = ""
1264 target_cc_ids = acc_target_cc_obj.search(cr, uid, [('cost_center_id', '=', analytic_acc_id)], context=context)
1265@@ -318,6 +422,7 @@
1266 field_list = ['instance_id', 'is_target', 'is_po_fo_cost_center', 'is_top_cost_center']
1267 for target_cc in acc_target_cc_obj.browse(cr, uid, target_cc_ids, fields_to_fetch=field_list, context=context):
1268 instance = target_cc.instance_id
1269+ all_instance_ids.append(instance.id)
1270 if instance.mission:
1271 missions.add(instance.mission)
1272 if target_cc.is_top_cost_center:
1273@@ -333,6 +438,7 @@
1274 'is_target_cc_instance_ids': target_instance_ids,
1275 'po_fo_cc_instance_ids': po_fo_instance_ids,
1276 'cc_missions': missions_str,
1277+ 'cc_instance_ids': all_instance_ids,
1278 }
1279 return res
1280
1281@@ -357,13 +463,22 @@
1282 'dest_cc_ids': fields.many2many('account.analytic.account', 'destination_cost_center_rel',
1283 'destination_id', 'cost_center_id', string='Cost Centers',
1284 domain="[('type', '!=', 'view'), ('category', '=', 'OC')]"),
1285- 'allow_all_cc': fields.boolean(string="Allow all Cost Centers"),
1286+ 'allow_all_cc': fields.boolean(string="Allow all Cost Centers"), # for the Destinations
1287+ 'allow_all_cc_with_fp': fields.boolean(string="Allow all Cost Centers"), # for the Funding Pools
1288 'dest_compatible_with_cc_ids': fields.function(_get_fake, method=True, store=False,
1289 string='Destinations compatible with the Cost Center',
1290 type='many2many', relation='account.analytic.account',
1291 fnct_search=_search_dest_compatible_with_cc_ids),
1292 'dest_without_cc': fields.function(_get_dest_without_cc, type='boolean', method=True, store=False,
1293 string="Destination allowing no Cost Center",),
1294+ 'fp_compatible_with_cc_ids': fields.function(_get_fake, method=True, store=False,
1295+ string='Funding Pools compatible with the Cost Center',
1296+ type='many2many', relation='account.analytic.account',
1297+ fnct_search=_search_fp_compatible_with_cc_ids),
1298+ 'fp_compatible_with_acc_dest_ids': fields.function(_get_fake, method=True, store=False,
1299+ string='Funding Pools compatible with the Account/Destination combination',
1300+ type='many2many', relation='account.analytic.account',
1301+ fnct_search=_search_fp_compatible_with_acc_dest_ids),
1302 'top_cc_instance_ids': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1303 string="Instances having the CC as Top CC",
1304 type="one2many", relation="msf.instance", multi="cc_instances"),
1305@@ -376,12 +491,21 @@
1306 'cc_missions': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1307 string="Missions where the CC is added to",
1308 type='char', multi="cc_instances"),
1309+ 'cc_instance_ids': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1310+ string="Instances where the CC is added to",
1311+ type="one2many", relation="msf.instance", multi="cc_instances"),
1312+ 'select_accounts_only': fields.boolean(string="Select Accounts Only"),
1313+ 'fp_account_ids': fields.many2many('account.account', 'fp_account_rel', 'fp_id', 'account_id', string='G/L Accounts',
1314+ domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True), ('active', '=', 't')]",
1315+ help="G/L accounts linked to the Funding Pool", order_by='code'),
1316 }
1317
1318 _defaults ={
1319 'date_start': lambda *a: (datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d'),
1320 'for_fx_gain_loss': lambda *a: False,
1321 'allow_all_cc': lambda *a: False,
1322+ 'allow_all_cc_with_fp': lambda *a: False,
1323+ 'select_accounts_only': lambda *a: False,
1324 }
1325
1326 def _check_code_unicity(self, cr, uid, ids, context=None):
1327@@ -453,30 +577,40 @@
1328 res['domain']['parent_id'] = [('category', '=', category), ('type', '=', 'view')]
1329 return res
1330
1331- def on_change_allow_all_cc(self, cr, uid, ids, allow_all_cc, dest_cc_ids, context=None):
1332+ 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):
1333 """
1334 If the user tries to tick the box "Allow all Cost Centers" whereas CC are selected,
1335 informs him that he has to remove the CC first
1336+ (acc_type = name of the Analytic Account Type to which the CC are linked, displayed in the warning msg)
1337 """
1338 res = {}
1339- if allow_all_cc and dest_cc_ids and dest_cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1340+ if allow_all_cc and cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1341+ # NOTE: the msg is stored in a variable on purpose, otherwise the ".po" translation files would wrongly contain Python code
1342+ msg = 'Please remove the Cost Centers linked to the %s before ticking this box.' % acc_type.title()
1343 warning = {
1344 'title': _('Warning!'),
1345- 'message': _('Please remove the Cost Centers linked to the Destination before ticking this box.')
1346+ 'message': _(msg)
1347 }
1348 res['warning'] = warning
1349- res['value'] = {'allow_all_cc': False, }
1350+ res['value'] = {field_name: False, }
1351 return res
1352
1353- def on_change_dest_cc_ids(self, cr, uid, ids, dest_cc_ids, context=None):
1354+ def on_change_allow_all_cc_with_fp(self, cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, context=None):
1355+ return self.on_change_allow_all_cc(cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, acc_type='funding pool',
1356+ field_name='allow_all_cc_with_fp', context=context)
1357+
1358+ def on_change_cc_ids(self, cr, uid, ids, cc_ids, field_name='allow_all_cc', context=None):
1359 """
1360 If at least a CC is selected, unticks the box "Allow all Cost Centers"
1361 """
1362 res = {}
1363- if dest_cc_ids and dest_cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1364- res['value'] = {'allow_all_cc': False, }
1365+ if cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1366+ res['value'] = {field_name: False, }
1367 return res
1368
1369+ def on_change_cc_with_fp(self, cr, uid, ids, cost_center_ids, context=None):
1370+ return self.on_change_cc_ids(cr, uid, ids, cost_center_ids, field_name='allow_all_cc_with_fp', context=context)
1371+
1372 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1373 if not context:
1374 context = {}
1375@@ -539,12 +673,12 @@
1376 vals['parent_id'] = funding_pool_parent
1377
1378 def remove_inappropriate_links(self, vals, context=None):
1379- '''
1380- Remove relations that are incoherent regarding the category selected. For instance an account with
1381- category "Funding Pool" can have associated cost centers, whereas a "Destination" shouldn't.
1382+ """
1383+ Removes relations that are inconsistent regarding the category selected. For instance an account with the
1384+ category "Funding Pool" can have associated cost centers, whereas a "Cost Center" shouldn't.
1385 (That would happen if the category is modified after that the relations have been created).
1386 :return: corrected vals
1387- '''
1388+ """
1389 if context is None:
1390 context = {}
1391 if 'category' in vals:
1392@@ -555,6 +689,15 @@
1393 if vals['category'] != 'FUNDING':
1394 vals['tuple_destination_account_ids'] = [(6, 0, [])]
1395 vals['cost_center_ids'] = [(6, 0, [])]
1396+ vals['allow_all_cc_with_fp'] = False # default value
1397+ vals['select_accounts_only'] = False
1398+ vals['fp_account_ids'] = [(6, 0, [])]
1399+ # Funding Pools: either "Account/Destination combinations" or "G/L accounts only" must be stored
1400+ if vals['category'] == 'FUNDING' and 'select_accounts_only' in vals:
1401+ if vals['select_accounts_only']:
1402+ vals['tuple_destination_account_ids'] = [(6, 0, [])]
1403+ else:
1404+ vals['fp_account_ids'] = [(6, 0, [])]
1405 return vals
1406
1407 def _check_date(self, vals):
1408@@ -720,6 +863,56 @@
1409 'target': 'current',
1410 }
1411
1412+ def get_cc_linked_to_fp(self, cr, uid, fp_id, context=None):
1413+ """
1414+ Returns a browse record list of all Cost Centers compatible with the Funding Pool in parameter:
1415+ - if "Allow all Cost Centers" is ticked: all CC linked to the prop. instance of the FP
1416+ - else all CC selected in the FP form.
1417+
1418+ Note: this method matches with what has been selected in the Cost centers tab of the FP form.
1419+ It returns an empty list for PF.
1420+ """
1421+ if context is None:
1422+ context = {}
1423+ cc_list = []
1424+ fp = self.browse(cr, uid, fp_id,
1425+ fields_to_fetch=['category', 'allow_all_cc_with_fp', 'instance_id', 'cost_center_ids'],
1426+ context=context)
1427+ if fp.category == 'FUNDING':
1428+ if fp.allow_all_cc_with_fp and fp.instance_id:
1429+ # inactive CC are included on purpose, to match with selectable CC in FP form
1430+ for cc_id in self.search(cr, uid, [('category', '=', 'OC'), ('type', '!=', 'view')], order='code', context=context):
1431+ cc = self.browse(cr, uid, cc_id, context=context)
1432+ if fp.instance_id.id in [inst.id for inst in cc.cc_instance_ids]:
1433+ cc_list.append(cc)
1434+ else:
1435+ cc_list = fp.cost_center_ids or []
1436+ return cc_list
1437+
1438+ def get_acc_dest_linked_to_fp(self, cr, uid, fp_id, context=None):
1439+ """
1440+ Returns a tuple of all combinations of (account_id, destination_id) compatible with the FP in parameter:
1441+ - if "Select Accounts Only" is ticked: the accounts selected and the Destinations compatible with them
1442+ - else the Account/Destination combinations selected.
1443+
1444+ Note: this method matches with what has been selected in the Accounts/Destinations tab of the FP form.
1445+ It returns an empty list for PF.
1446+ """
1447+ if context is None:
1448+ context = {}
1449+ combinations = []
1450+ fp = self.browse(cr, uid, fp_id,
1451+ fields_to_fetch=['category', 'select_accounts_only', 'fp_account_ids', 'tuple_destination_account_ids'],
1452+ context=context)
1453+ if fp.category == 'FUNDING':
1454+ if fp.select_accounts_only:
1455+ for account in fp.fp_account_ids:
1456+ for dest in account.destination_ids:
1457+ combinations.append((account.id, dest.id))
1458+ else:
1459+ combinations = [(t.account_id.id, t.destination_id.id) for t in fp.tuple_destination_account_ids if not t.disabled]
1460+ return combinations
1461+
1462 def button_cc_clear(self, cr, uid, ids, context=None):
1463 self.write(cr, uid, ids, {'cost_center_ids':[(6, 0, [])]}, context=context)
1464 return True
1465@@ -735,6 +928,13 @@
1466 self.write(cr, uid, ids, {'tuple_destination_account_ids':[(6, 0, [])]}, context=context)
1467 return True
1468
1469+ def button_fp_account_clear(self, cr, uid, ids, context=None):
1470+ """
1471+ Removes all G/L accounts selected in the Funding Pool view
1472+ """
1473+ self.write(cr, uid, ids, {'fp_account_ids': [(6, 0, [])]}, context=context)
1474+ return True
1475+
1476 def get_destinations_by_accounts(self, cr, uid, ids, context=None):
1477 """
1478 Returns a view with the Destinations by accounts (for the FP selected if any, otherwise for all the FP)
1479
1480=== modified file 'bin/addons/financing_contract/contract.py'
1481--- bin/addons/financing_contract/contract.py 2019-10-10 16:20:57 +0000
1482+++ bin/addons/financing_contract/contract.py 2020-11-02 12:53:09 +0000
1483@@ -62,6 +62,10 @@
1484 return True
1485
1486 def create(self, cr, uid, vals, context=None):
1487+ if context is None:
1488+ context = {}
1489+ analytic_acc_obj = self.pool.get('account.analytic.account')
1490+ format_obj = self.pool.get('financing.contract.format')
1491 # US-113: Check if the call is from sync update
1492 if context.get('sync_update_execution') and vals.get('contract_id', False):
1493 # US-113: and if there is any financing contract existed for this format, if no, then ignore this call
1494@@ -75,26 +79,18 @@
1495
1496 #US-345: the following block cannot be executed in the sync context, because it would then reset all costcenters from the funding pools!
1497 # making that the deleted costcenters from the sender were not taken into account
1498- if not context.get('sync_update_execution') and 'contract_id' in vals and 'funding_pool_id' in vals:
1499- # get the cc ids from for this funding pool
1500- quad_obj = self.pool.get('financing.contract.account.quadruplet')
1501- quad_ids = quad_obj.search(cr, uid, [('funding_pool_id','=',vals['funding_pool_id'])],context=context)
1502- quad_rows = quad_obj.browse(cr, uid, quad_ids,context=context)
1503- quad_cc_ids = []
1504- for quad in quad_rows:
1505- cc_id_temp = quad.cost_center_id.id
1506- if cc_id_temp not in quad_cc_ids:
1507- quad_cc_ids.append(cc_id_temp)
1508+ if not context.get('sync_update_execution') and vals.get('contract_id') and vals.get('funding_pool_id'):
1509+ # get the Cost Centers linked to the Funding Pool
1510+ fp_cc_ids = [c.id for c in analytic_acc_obj.get_cc_linked_to_fp(cr, uid, vals['funding_pool_id'], context=context)]
1511
1512 # get the format instance
1513- format_obj = self.pool.get('financing.contract.format')
1514 cc_rows = format_obj.browse(cr, uid, vals['contract_id'], context=context).cost_center_ids
1515 cc_ids = []
1516 for cc in cc_rows:
1517 cc_ids.append(cc.id)
1518
1519 # append the ccs from the fp only if not already there
1520- cc_ids = list(set(cc_ids).union(quad_cc_ids))
1521+ cc_ids = list(set(cc_ids).union(fp_cc_ids))
1522 # replace the associated cc list -NOT WORKING
1523 format_obj.write(cr, uid, vals['contract_id'],{'cost_center_ids':[(6,0,cc_ids)]}, context=context)
1524 # UFTP-121: Check that FP is not used yet.
1525@@ -337,6 +333,7 @@
1526 ondelete="cascade", required=True),
1527 'fp_added_flag': fields.boolean('Flag when new FP is added'),
1528 'instance_level': fields.function(_get_instance_level, method=True, string="Current instance level", type="char", readonly=True), # UFTP-343
1529+ 'quad_gen_date': fields.datetime('Date of last generation of quad'),
1530 }
1531
1532 _defaults = {
1533
1534=== modified file 'bin/addons/financing_contract/financing_contract_account_quadruplet.py'
1535--- bin/addons/financing_contract/financing_contract_account_quadruplet.py 2017-10-02 15:51:29 +0000
1536+++ bin/addons/financing_contract/financing_contract_account_quadruplet.py 2020-11-02 12:53:09 +0000
1537@@ -21,18 +21,48 @@
1538
1539 from osv import fields, osv
1540 from tools import sql
1541+import time
1542
1543 class financing_contract_account_quadruplet(osv.osv):
1544 _name = 'financing.contract.account.quadruplet'
1545 _rec_name = 'cost_center_id'
1546 _description = 'FP / CC / destination valid values view'
1547- _auto = False
1548-
1549+ _log_access = False
1550+ _auto = True
1551+
1552+ def migrate_old_quad(self, cr, uid, ids, context=None):
1553+ '''
1554+ ids: list of record in the old view
1555+ return list of new record
1556+ '''
1557+
1558+ new_ids = []
1559+ for old_id in ids:
1560+ # DO UPDATE: to return an id
1561+ cr.execute('''
1562+ INSERT INTO financing_contract_account_quadruplet
1563+ (account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id)
1564+ ( select
1565+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
1566+ from
1567+ financing_contract_account_quadruplet_old
1568+ where
1569+ id=%s
1570+ ) ON CONFLICT ON CONSTRAINT financing_contract_account_quadruplet_check_unique DO UPDATE SET disabled=EXCLUDED.disabled
1571+ RETURNING id ''', (old_id,))
1572+ ret = cr.fetchone()
1573+ if ret:
1574+ new_ids.append(ret[0])
1575+ return new_ids
1576
1577 def _auto_init(self, cr, context=None):
1578+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet')
1579+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet_view')
1580 res = super(financing_contract_account_quadruplet, self)._auto_init(cr, context)
1581- sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet')
1582- cr.execute("""CREATE OR REPLACE VIEW financing_contract_account_quadruplet AS (
1583+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet_old')
1584+
1585+ # old sql view kept to manage migration and old sync updates
1586+ cr.execute('''CREATE OR REPLACE VIEW financing_contract_account_quadruplet_old AS (
1587 SELECT abs(('x'||substr(md5(fp.code || cc.code || lnk.name),1,16))::bit(32)::int) as id,
1588 lnk.destination_id AS account_destination_id, cc.id AS cost_center_id, fp.id AS funding_pool_id, lnk.name AS account_destination_name, lnk.account_id, lnk.disabled, lnk.id as account_destination_link_id
1589 FROM account_analytic_account fp,
1590@@ -44,9 +74,133 @@
1591 AND fpacc.cost_center_id = cc.id
1592 AND lnk.id = fpad.tuple_id
1593 AND fp.id = fpad.funding_pool_id
1594- ORDER BY lnk.name, cc.code DESC)""")
1595+ ORDER BY lnk.name, cc.code DESC)
1596+ ''')
1597+
1598+ cr.execute("""CREATE OR REPLACE VIEW financing_contract_account_quadruplet_view AS (
1599+ SELECT account_destination_id, cost_center_id, funding_pool_id, account_destination_name, account_id, disabled, account_destination_link_id FROM
1600+ (
1601+ -- all cc = f, G/L = f
1602+ SELECT
1603+ lnk.destination_id AS account_destination_id, cc.id AS cost_center_id, fp.id AS funding_pool_id, lnk.name AS account_destination_name, lnk.account_id, lnk.disabled, lnk.id as account_destination_link_id
1604+ FROM account_analytic_account fp,
1605+ account_analytic_account cc,
1606+ funding_pool_associated_cost_centers fpacc,
1607+ funding_pool_associated_destinations fpad,
1608+ account_destination_link lnk
1609+ WHERE
1610+ fpacc.funding_pool_id = fp.id AND
1611+ fpacc.cost_center_id = cc.id AND
1612+ lnk.id = fpad.tuple_id AND
1613+ fp.id = fpad.funding_pool_id
1614+
1615+ UNION
1616+
1617+ -- all cc = t, G/L = t
1618+ select
1619+ lnk.destination_id AS account_destination_id, cc.id AS cost_center_id, fp.id AS funding_pool_id, lnk.name AS account_destination_name, lnk.account_id, lnk.disabled, lnk.id as account_destination_link_id
1620+ FROM
1621+ account_analytic_account fp,
1622+ account_analytic_account cc,
1623+ fp_account_rel,
1624+ account_target_costcenter target,
1625+ account_destination_link lnk,
1626+ account_account gl_account
1627+ where
1628+ fp.allow_all_cc_with_fp = 't' and
1629+ cc.type != 'view' and
1630+ cc.category = 'OC' and
1631+ target.cost_center_id = cc.id and
1632+ target.instance_id = fp.instance_id and
1633+ fp.select_accounts_only = 't' and
1634+ fp_account_rel.fp_id = fp.id and
1635+ fp_account_rel.account_id= gl_account.id and
1636+ lnk.account_id = gl_account.id
1637+
1638+ UNION
1639+
1640+ -- all cc = f, G/L = t
1641+ select
1642+ lnk.destination_id AS account_destination_id, cc.id AS cost_center_id, fp.id AS funding_pool_id, lnk.name AS account_destination_name, lnk.account_id, lnk.disabled, lnk.id as account_destination_link_id
1643+ FROM
1644+ account_analytic_account fp,
1645+ account_analytic_account cc,
1646+ funding_pool_associated_cost_centers fpacc,
1647+ fp_account_rel,
1648+ account_destination_link lnk,
1649+ account_account gl_account
1650+ where
1651+ fp.allow_all_cc_with_fp = 'f' and
1652+ fpacc.funding_pool_id = fp.id and
1653+ fpacc.cost_center_id = cc.id and
1654+ fp.select_accounts_only = 't' and
1655+ fp_account_rel.fp_id = fp.id and
1656+ fp_account_rel.account_id= gl_account.id and
1657+ lnk.account_id = gl_account.id
1658+
1659+ UNION
1660+
1661+ -- all cc = t , G/L = f
1662+ select
1663+ lnk.destination_id AS account_destination_id, cc.id AS cost_center_id, fp.id AS funding_pool_id, lnk.name AS account_destination_name, lnk.account_id, lnk.disabled, lnk.id as account_destination_link_id
1664+ FROM
1665+ account_analytic_account fp,
1666+ account_analytic_account cc,
1667+ funding_pool_associated_destinations fpad,
1668+ account_target_costcenter target,
1669+ account_destination_link lnk
1670+ where
1671+ fp.allow_all_cc_with_fp = 't' and
1672+ cc.type != 'view' and
1673+ cc.category = 'OC' and
1674+ target.cost_center_id = cc.id and
1675+ target.instance_id = fp.instance_id and
1676+ fp.select_accounts_only = 'f' and
1677+ lnk.id = fpad.tuple_id and
1678+ fp.id = fpad.funding_pool_id
1679+ ) AS combinations
1680+ )""")
1681 return res
1682
1683+ def gen_quadruplet(self, cr, uid, context=None):
1684+ '''
1685+ triggered by unifield-web to generate the list of quadruplets for this contract
1686+ record the last generation date to refresh only if the contract, a dest link or a ana. acccount is modified
1687+ '''
1688+ if context is None:
1689+ context = {}
1690+ contract_id = context.get('contract_id', False)
1691+ if contract_id:
1692+ ctr_obj = self.pool.get('financing.contract.contract')
1693+ contract = ctr_obj.browse(cr, uid, context['contract_id'], fields_to_fetch=['funding_pool_ids', 'cost_center_ids', 'quad_gen_date'], context=context)
1694+ cr.execute('''select max(last_modification) from ir_model_data where module='sd' and (
1695+ model in ('account.analytic.account', 'account.destination.link') or (model = 'financing.contract.contract' and res_id = %s)
1696+ )''', (contract_id,))
1697+ last_obj_modified = cr.fetchone()[0]
1698+ if not contract.quad_gen_date or last_obj_modified > contract.quad_gen_date or contract.quad_gen_date > time.strftime('%Y-%m-%d %H:%M:%S'):
1699+ # ignore quad_gen_date in the future
1700+ cc_ids = [cc.id for cc in contract.cost_center_ids]
1701+ fp_ids = [fp.funding_pool_id.id for fp in contract.funding_pool_ids]
1702+ if not cc_ids:
1703+ # do not traceback if cc / fp not set on contract
1704+ cc_ids = [0]
1705+ if not fp_ids:
1706+ fp_ids = [0]
1707+ cr.execute('''
1708+ INSERT INTO financing_contract_account_quadruplet
1709+ (account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id)
1710+ (select
1711+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
1712+ from
1713+ financing_contract_account_quadruplet_view
1714+ where
1715+ funding_pool_id in %s and
1716+ cost_center_id in %s
1717+ )
1718+ ON CONFLICT ON CONSTRAINT financing_contract_account_quadruplet_check_unique DO UPDATE SET disabled=EXCLUDED.disabled''', (tuple(fp_ids), tuple(cc_ids)))
1719+ cr.execute('update financing_contract_contract set quad_gen_date=%s where id=%s', (last_obj_modified, contract_id))
1720+
1721+ return True
1722
1723 # The result set with {ID:Flag} if Flag=True, the line will be grey, otherwise, it is selectable
1724 def _get_used_in_contract(self, cr, uid, ids, field_name, arg, context=None):
1725@@ -67,8 +221,8 @@
1726 # TODO this should be renamed format_id
1727 cr.execute('''select account_quadruplet_id
1728 from financing_contract_actual_account_quadruplets
1729- where actual_line_id in (select id from financing_contract_format_line
1730- where format_id = %s and is_quadruplet is true)''', (contract.format_id.id,))
1731+ where account_quadruplet_id in %s and actual_line_id in (select id from financing_contract_format_line
1732+ where format_id = %s and is_quadruplet is true)''', (tuple(ids), contract.format_id.id,))
1733 rows = cr.fetchall()
1734 for id in [x[0] for x in rows]:
1735 exclude[id] = True
1736@@ -78,9 +232,13 @@
1737 if not active_id or line.id != active_id:
1738 for account_destination in line.account_destination_ids:
1739 # search the quadruplet to exclude
1740- quadruplet_ids_to_exclude = self.search(cr, uid, [('account_id', '=', account_destination.account_id.id),('account_destination_id','=',account_destination.destination_id.id)])
1741+ quadruplet_ids_to_exclude = self.search(cr, uid, [('id', 'in', ids), ('account_id', '=', account_destination.account_id.id),('account_destination_id','=',account_destination.destination_id.id)])
1742 for item in quadruplet_ids_to_exclude:
1743 exclude[item] = True
1744+ for account in line.reporting_account_ids:
1745+ # exclude the quadruplets when the account has been selected in lines with "accounts only"
1746+ for quad in self.search(cr, uid, [('account_id', '=', account.id)], order='NO_ORDER', context=context):
1747+ exclude[quad] = True
1748
1749 for id in ids:
1750 ids_to_exclude[id] = id in exclude
1751@@ -90,35 +248,20 @@
1752 res = {}
1753 if context is None:
1754 context = {}
1755- exclude = {}
1756+ res = {}
1757
1758 if not context.get('contract_id'):
1759 for id in ids:
1760 res[id] = False
1761 return res
1762
1763- ctr_obj = self.pool.get('financing.contract.contract')
1764- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1765- # financing_contract_funding_pool_line.contract_id is a FK for financing_contract_format.id
1766- # TODO this should be renamed format_id during refactoring
1767- exclude = {}
1768- cr.execute('''select id from financing_contract_account_quadruplet
1769- where funding_pool_id in
1770- (select funding_pool_id
1771- from financing_contract_funding_pool_line
1772- where contract_id = %s)
1773- and exists (select 'X'
1774- from financing_contract_cost_center cc
1775- where cc.contract_id = %s
1776- and cc.cost_center_id =
1777- financing_contract_account_quadruplet.cost_center_id)''', (contract.format_id.id,contract.format_id.id))
1778- for id in [x[0] for x in cr.fetchall()]:
1779- exclude[id] = True
1780- for id in ids:
1781- res[id] = id in exclude
1782+ for _id in ids:
1783+ res[_id] = False
1784+
1785+ for _id in self.search(cr, uid, [('can_be_used', '=', True)], context=context):
1786+ res[_id] = True
1787 return res
1788
1789-
1790 def _search_can_be(self, cr, uid, ids, field_name, arg, context=None):
1791 res = {}
1792 if context is None:
1793@@ -130,67 +273,30 @@
1794 return res
1795
1796 ctr_obj = self.pool.get('financing.contract.contract')
1797- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1798- cr.execute('''select id from financing_contract_account_quadruplet
1799- where funding_pool_id in
1800- (select funding_pool_id
1801- from financing_contract_funding_pool_line
1802- where contract_id = %s)
1803- and exists (select 'X'
1804- from financing_contract_cost_center cc
1805- where cc.contract_id = %s
1806- and cc.cost_center_id =
1807- financing_contract_account_quadruplet.cost_center_id)''', (contract.format_id.id,contract.format_id.id))
1808- someids = []
1809- someids += [x[0] for x in cr.fetchall()]
1810- return [('id','in',someids)]
1811-
1812-
1813-
1814- def _search_used_in_contract(self, cr, uid, obj, name, args, context=None):
1815- if not args:
1816- return []
1817- if context is None:
1818- context = {}
1819- assert args[0][1] == '=' and args[0][2], 'Filter not implemented'
1820- if not context.get('contract_id'):
1821- return []
1822-
1823- ctr_obj = self.pool.get('financing.contract.contract')
1824- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1825-
1826- exclude = []
1827- for line in contract.actual_line_ids:
1828- if context.get('active_id', False) and line.id != context['active_id']:
1829- for account_destination in line.account_destination_ids:
1830- cr.execute('''select account_quadruplet_id
1831- from financing_contract_actual_account_quadruplets
1832- where actual_line_id in (select l.id from financing_contract_contract c,
1833- financing_contract_format f,
1834- financing_contract_format_line l
1835- where c.id = %s
1836- and f.id = c.format_id
1837- and l.format_id = f.id)''', (contract.format_id.id,))
1838- exclude += [x[0] for x in cr.fetchall()]
1839- for account_quadruplet in line.account_quadruplet_ids:
1840- exclude.append(account_quadruplet.id)
1841- return [('id', 'not in', exclude)]
1842-
1843- #columns for view
1844+ contract = ctr_obj.browse(cr, uid, context['contract_id'], fields_to_fetch=['funding_pool_ids', 'cost_center_ids'])
1845+ cc_ids = [cc.id for cc in contract.cost_center_ids]
1846+ fp_ids = [fp.funding_pool_id.id for fp in contract.funding_pool_ids]
1847+ return [('cost_center_id', 'in', cc_ids), ('funding_pool_id', 'in', fp_ids)]
1848+
1849 _columns = {
1850- 'account_destination_id': fields.many2one('account.destination.link', 'Account/Destination', relate=True, readonly=True),
1851- 'cost_center_id': fields.many2one('account.analytic.account', 'Cost Centre', relate=True, readonly=True),
1852- 'funding_pool_id': fields.many2one('account.analytic.account', 'Funding Pool', relate=True, readonly=True),
1853- 'account_destination_name': fields.char('Account', size=64, readonly=True),
1854- 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used', fnct_search=_search_used_in_contract),
1855+ 'account_destination_id': fields.many2one('account.analytic.account', 'Destination', relate=True, readonly=True, select=1),
1856+ 'cost_center_id': fields.many2one('account.analytic.account', 'Cost Centre', relate=True, readonly=True, select=1),
1857+ 'funding_pool_id': fields.many2one('account.analytic.account', 'Funding Pool', relate=True, readonly=True, select=1),
1858+ 'account_destination_name': fields.char('Account', size=64, readonly=True, select=1),
1859+ 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used'),
1860 'can_be_used': fields.function(_can_be_used_in_contract, method=True, type='boolean', string='Can', fnct_search=_search_can_be),
1861- 'account_id': fields.many2one('account.destination.link', 'Account ID', relate=True, readonly=True),
1862- 'account_destination_link_id': fields.many2one('account.destination.link', 'Link id', readonly=True),
1863+ 'account_id': fields.many2one('account.account', 'Account ID', relate=True, readonly=True, select=1),
1864+ 'account_destination_link_id': fields.many2one('account.destination.link', 'Link id', readonly=True, select=1),
1865 'disabled': fields.boolean('Disabled'),
1866 }
1867
1868- _order = 'account_destination_name asc, funding_pool_id asc, cost_center_id asc'
1869+ _sql_constraints = {
1870+ ('check_unique',
1871+ 'unique (account_destination_id, cost_center_id, funding_pool_id, account_id, account_destination_link_id)',
1872+ 'not unique!')
1873+ }
1874+ _order = 'account_destination_name asc, funding_pool_id asc, cost_center_id asc, id'
1875+
1876
1877 financing_contract_account_quadruplet()
1878 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1879-
1880
1881=== modified file 'bin/addons/financing_contract/financing_contract_view.xml'
1882--- bin/addons/financing_contract/financing_contract_view.xml 2020-01-30 10:18:45 +0000
1883+++ bin/addons/financing_contract/financing_contract_view.xml 2020-11-02 12:53:09 +0000
1884@@ -105,20 +105,54 @@
1885 <field name="overhead_type" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1886 <field name="overhead_percentage" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1887 </group>
1888- <button name="button_delete_all_quads" type="object" string="Remove all quads" icon="gtk-clear" colspan="1"
1889- attrs="{'invisible': [('is_quadruplet', '=', False)]}" />
1890- <button name="button_delete_all_couples" type="object" string="Remove all couples" icon="gtk-clear" colspan="1"
1891- attrs="{'invisible': [('is_quadruplet', '=', True)]}" />
1892- <field name="is_quadruplet" attrs="{'invisible': [('line_type', '=', 'view')]}"/>
1893+ <group colspan="6" col="20">
1894+ <button name="button_delete_all_quads" type="object" string="Remove all quads"
1895+ icon="gtk-clear" colspan="4"
1896+ attrs="{'invisible': ['|',
1897+ ('is_quadruplet', '=', False),
1898+ ('line_type', '=', 'view')]}" />
1899+ <button name="button_delete_all_couples" type="object" string="Remove all couples"
1900+ icon="gtk-clear" colspan="4"
1901+ attrs="{'invisible': ['|', '|',
1902+ ('is_quadruplet', '=', True),
1903+ ('reporting_select_accounts_only', '=', True),
1904+ ('line_type', '=', 'view')]}"
1905+ />
1906+ <button name="button_remove_all_accounts" type="object" string="Remove all accounts"
1907+ icon="gtk-clear" colspan="4"
1908+ attrs="{'invisible': ['|',
1909+ ('reporting_select_accounts_only', '=', False),
1910+ ('line_type', '=', 'view')]}" />
1911+ <label string="" colspan="1"/>
1912+ <field name="is_quadruplet" attrs="{'invisible': [('line_type', '=', 'view')]}"
1913+ colspan="2"
1914+ on_change="on_change_is_quadruplet(is_quadruplet)"
1915+ />
1916+ <field name="reporting_select_accounts_only" attrs="{'invisible': [('line_type', '=', 'view')]}"
1917+ colspan="2"
1918+ on_change="on_change_reporting_select_accounts_only(reporting_select_accounts_only)"
1919+ />
1920+ <label string="" colspan="3"/>
1921+ </group>
1922 <field name="account_destination_ids" colspan="4" string="Account/Destination" nolabel="1"
1923 context="{'search_default_active': 1}"
1924- attrs="{'invisible': [('|'), ('line_type', '=', 'view'), ('is_quadruplet', '=', True)]}">
1925- </field>
1926+ attrs="{'invisible': ['|', '|',
1927+ ('line_type', '=', 'view'),
1928+ ('is_quadruplet', '=', True),
1929+ ('reporting_select_accounts_only', '=', True)]}"
1930+ />
1931 <field name="account_quadruplet_ids" colspan="4" string="Account/Destination/Funding Pool/Cost Centre" nolabel="1"
1932 context="{'search_default_active': 1}"
1933 attrs="{'invisible': [('|'), ('line_type', '=', 'view'), ('is_quadruplet', '!=', True)]}"
1934 domain="[('can_be_used','=',True)]">
1935 </field>
1936+ <field name="reporting_account_ids" colspan="4" string="G/L Accounts" nolabel="1"
1937+ context="{'from_grant_management': True, 'search_default_active': 1}"
1938+ attrs="{'invisible': ['|',
1939+ ('line_type', '=', 'view'),
1940+ ('reporting_select_accounts_only', '=', False)]}"
1941+ domain="[('selectable_in_contract', '=', True)]"
1942+ />
1943 </form>
1944 </field>
1945 </page>
1946@@ -175,7 +209,6 @@
1947 <field name="type">tree</field>
1948 <field name="arch" type="xml">
1949 <tree string="Account/Destination/Funding Pool/Cost Centre" colors="grey:used_in_contract;red:disabled" notselectable="used_in_contract" editable="top" noteditable="1">
1950- <field name="can_be_used" invisible="1"/>
1951 <field name="account_destination_name"/>
1952 <field name="funding_pool_id"/>
1953 <field name="cost_center_id"/>
1954@@ -304,9 +337,19 @@
1955 <field name="overhead_type" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1956 <field name="overhead_percentage" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1957 </group>
1958+ <field name="reporting_select_accounts_only" attrs="{'invisible': [('line_type', '=', 'view')]}"
1959+ colspan="2"/>
1960 <field name="account_destination_ids" colspan="4" string="Account/Destination"
1961 context="context"
1962- attrs="{'invisible': [('line_type', '=', 'view')]}"/>
1963+ attrs="{'invisible': ['|',
1964+ ('line_type', '=', 'view'),
1965+ ('reporting_select_accounts_only', '=', True)]}"/>
1966+ <field name="reporting_account_ids" colspan="4" string="G/L Accounts"
1967+ context="{'from_grant_management': True, 'search_default_active': 1}"
1968+ attrs="{'invisible': ['|',
1969+ ('line_type', '=', 'view'),
1970+ ('reporting_select_accounts_only', '=', False)]}"
1971+ />
1972 </form>
1973 </field>
1974 </page>
1975
1976=== modified file 'bin/addons/financing_contract/format.py'
1977--- bin/addons/financing_contract/format.py 2015-05-28 08:50:00 +0000
1978+++ bin/addons/financing_contract/format.py 2020-11-02 12:53:09 +0000
1979@@ -97,39 +97,17 @@
1980 duplet_ids_to_exclude = self.search(cr, uid, [('account_id', '=', account_quadruplet.account_id.id),('destination_id','=',account_quadruplet.account_destination_id.id)])
1981 for item in duplet_ids_to_exclude:
1982 exclude[item] = True
1983+ for account in line.reporting_account_ids:
1984+ # exclude the acc/dest combinations when the account has been selected in lines with "accounts only"
1985+ for acc_dest in self.search(cr, uid, [('account_id', '=', account.id)], order='NO_ORDER', context=context):
1986+ exclude[acc_dest] = True
1987
1988 for id in ids:
1989 ids_to_exclude[id] = id in exclude
1990 return ids_to_exclude
1991
1992- def _search_used_in_contract(self, cr, uid, obj, name, args, context=None):
1993- if not args:
1994- return []
1995- if context is None:
1996- context = {}
1997- assert args[0][1] == '=' and args[0][2], 'Filter not implemented'
1998- if not context.get('contract_id') and not context.get('donor_id'):
1999- return []
2000-
2001- if context.get('contract_id'):
2002- ctr_obj = self.pool.get('financing.contract.contract')
2003- id_toread = context['contract_id']
2004- elif context.get('donor_id'):
2005- ctr_obj = self.pool.get('financing.contract.donor')
2006- id_toread = context['donor_id']
2007-
2008- exclude = {}
2009- for line in ctr_obj.browse(cr, uid, id_toread).actual_line_ids:
2010- if not context.get('active_id', False) or line.id != context['active_id']:
2011- for account_destination in line.account_destination_ids:
2012- exclude[account_destination.id] = True
2013- for account_quadruplet in line.account_quadruplet_ids:
2014- exclude[account_quadruplet.account_destination_id.id] = True
2015-
2016- return [('id', 'not in', exclude.keys())]
2017-
2018 _columns = {
2019- 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used', fnct_search=_search_used_in_contract),
2020+ 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used'),
2021 }
2022
2023 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2024
2025=== modified file 'bin/addons/financing_contract/format_line.py'
2026--- bin/addons/financing_contract/format_line.py 2019-10-30 16:33:21 +0000
2027+++ bin/addons/financing_contract/format_line.py 2020-11-02 12:53:09 +0000
2028@@ -22,7 +22,7 @@
2029 from osv import fields, osv
2030 from analytic_distribution.destination_tools import many2many_sorted
2031 from account_override import ACCOUNT_RESTRICTED_AREA
2032-
2033+from tools.safe_eval import safe_eval
2034
2035 class financing_contract_format_line(osv.osv):
2036
2037@@ -63,50 +63,47 @@
2038 return domain
2039
2040 # get list of accounts for duplet format lines
2041- def _create_account_couple_domain(self, account_destination_list, general_domain):
2042- if len(account_destination_list) == 0:
2043- return False # Just make this condition to False
2044- elif len(account_destination_list) == 1:
2045- temp_domain = ['&',
2046- ('general_account_id', '=', account_destination_list[0].account_id.id),
2047- ('destination_id', '=', account_destination_list[0].destination_id.id)]
2048-
2049- return temp_domain
2050- else:
2051- firstElement = self._create_account_couple_domain([account_destination_list[0]], general_domain)
2052- secondElement = self._create_account_couple_domain(account_destination_list[1:], general_domain)
2053-
2054- if firstElement and secondElement:
2055- return ['|'] + firstElement + secondElement
2056- elif firstElement:
2057- return firstElement
2058- return secondElement
2059+ def _create_account_couple_domain(self, account_destination_list):
2060+ """
2061+ Returns the domain corresponding to the list of acc/dest in param.
2062+ """
2063+ dom = []
2064+ if not account_destination_list:
2065+ return dom
2066+ first = True
2067+ for account_dest in account_destination_list:
2068+ dom += ['&', ('general_account_id', '=', account_dest.account_id.id), ('destination_id', '=', account_dest.destination_id.id)]
2069+ if not first:
2070+ dom.insert(0, '|')
2071+ else:
2072+ first = False
2073+ return dom
2074
2075 # get list of accounts for quadruplet format lines
2076- def _create_account_quadruplet_domain(self, account_quadruplet_list, funding_pool_ids=False):
2077- if len(account_quadruplet_list) == 0:
2078- return False
2079- elif len(account_quadruplet_list) == 1:
2080- if account_quadruplet_list[0].funding_pool_id.id in funding_pool_ids:
2081- quad_element = account_quadruplet_list[0]
2082- return ['&',
2083- '&',
2084- ('general_account_id', '=', quad_element.account_id.id),
2085- ('destination_id', '=', quad_element.account_destination_id.id),
2086- '&',
2087- ('cost_center_id', '=', quad_element.cost_center_id.id),
2088- ('account_id', '=', quad_element.funding_pool_id.id)]
2089- else:
2090- return False
2091- else:
2092- firstElement = self._create_account_quadruplet_domain([account_quadruplet_list[0]], funding_pool_ids)
2093- secondElement = self._create_account_quadruplet_domain(account_quadruplet_list[1:], funding_pool_ids)
2094-
2095- if firstElement and secondElement:
2096- return ['|'] + firstElement + secondElement
2097- elif firstElement:
2098- return firstElement
2099- return secondElement
2100+ def _create_account_quadruplet_domain(self, account_quadruplet_list, funding_pool_ids=None):
2101+ """
2102+ Returns the domain corresponding to the list of quadruplets in param.
2103+ """
2104+ dom = []
2105+ if not account_quadruplet_list:
2106+ return dom
2107+ if funding_pool_ids is None:
2108+ funding_pool_ids = []
2109+ first = True
2110+ for quad in account_quadruplet_list:
2111+ if quad.funding_pool_id.id in funding_pool_ids:
2112+ dom += ['&',
2113+ '&',
2114+ '&',
2115+ ('general_account_id', '=', quad.account_id.id),
2116+ ('destination_id', '=', quad.account_destination_id.id),
2117+ ('cost_center_id', '=', quad.cost_center_id.id),
2118+ ('account_id', '=', quad.funding_pool_id.id)]
2119+ if not first:
2120+ dom.insert(0, '|')
2121+ else:
2122+ first = False
2123+ return dom
2124
2125 def _get_number_of_childs(self, cr, uid, ids, field_name=None, arg=None, context=None):
2126 # Verifications
2127@@ -150,14 +147,27 @@
2128 self.write(cr, uid, ids, {'account_destination_ids':[(6, 0, [])]}, context=context )
2129 return True
2130
2131+ def button_remove_all_accounts(self, cr, uid, ids, context=None):
2132+ """
2133+ Removes all G/L accounts selected in the Reporting lines wizard
2134+ """
2135+ self.write(cr, uid, ids, {'reporting_account_ids': [(6, 0, [])]}, context=context)
2136+ return True
2137+
2138 # Get the list of accounts for both duplet and quadruplet
2139 def _get_accounts_couple_and_quadruplets(self, browse_line):
2140+ """
2141+ Returns a dict with a list of browse records for acc/dest, quadruplets and "accounts only"
2142+ """
2143 account_destination_result = []
2144 account_quadruplet_result = []
2145-
2146+ account_gl_result = []
2147 if browse_line.line_type != 'view':
2148 if browse_line.is_quadruplet:
2149 account_quadruplet_result = [account_quadruplet for account_quadruplet in browse_line.account_quadruplet_ids]
2150+ elif browse_line.reporting_select_accounts_only:
2151+ # this syntax's goal is to get a list of browse records instead of a browse_record_list
2152+ account_gl_result = [a for a in browse_line.reporting_account_ids]
2153 else:
2154 account_destination_result = [account_destination for account_destination in browse_line.account_destination_ids]
2155 else:
2156@@ -165,8 +175,12 @@
2157 temp = self._get_accounts_couple_and_quadruplets(child_line)
2158 account_destination_result += temp['account_destination_list']
2159 account_quadruplet_result += temp['account_quadruplet_list']
2160- return {'account_destination_list': account_destination_result,
2161- 'account_quadruplet_list': account_quadruplet_result}
2162+ account_gl_result += temp['account_gl_list']
2163+ return {
2164+ 'account_destination_list': account_destination_result,
2165+ 'account_quadruplet_list': account_quadruplet_result,
2166+ 'account_gl_list': account_gl_result,
2167+ }
2168
2169 def _get_general_domain(self, cr, uid, browse_format, domain_type, context=None):
2170 # Method to get the domain (allocated or project) of a line
2171@@ -205,6 +219,8 @@
2172 funding_pool_ids = [x for x in funding_pool_ids if x not in fp_ids]
2173 funding_pool_domain = self._create_domain('account_id', funding_pool_ids)
2174 gen_domain['funding_pool_domain'] = funding_pool_domain
2175+ else:
2176+ gen_domain['funding_pool_domain'] = "('account_id', 'in', [])"
2177
2178 gen_domain['funding_pool_ids'] = [x.id for x in funding_pool_ids]
2179 return gen_domain
2180@@ -221,23 +237,35 @@
2181 if format.eligibility_from_date and format.eligibility_to_date:
2182 #### DUY US-385: MOVE THIS TO OUTSIDE OF THE ALL THE LOOPS
2183 general_domain = self._get_general_domain(cr, uid, format, domain_type, context=context)
2184+ accounts_criteria = ['&', '&', ] + non_corrected_domain
2185+ acc_domains = []
2186
2187 # Account + destination domain
2188 account_destination_quadruplet_ids = self._get_accounts_couple_and_quadruplets(browse_line)
2189- account_couple_domain = self._create_account_couple_domain(account_destination_quadruplet_ids['account_destination_list'], False)
2190+ account_couple_domain = self._create_account_couple_domain(account_destination_quadruplet_ids['account_destination_list'])
2191+ if account_couple_domain:
2192+ acc_domains += [account_couple_domain]
2193 # get the criteria for accounts of quadruplet mode
2194 account_quadruplet_domain = self._create_account_quadruplet_domain(account_destination_quadruplet_ids['account_quadruplet_list'], general_domain['funding_pool_ids'])
2195-
2196- if not account_couple_domain and not account_quadruplet_domain:
2197- return [('id', '=', '-1')]
2198-
2199- accounts_criteria = ['&', '&', ] + non_corrected_domain
2200- if account_couple_domain and account_quadruplet_domain:
2201- accounts_criteria += ['|'] + account_couple_domain + account_quadruplet_domain
2202- elif account_couple_domain:
2203- accounts_criteria += account_couple_domain
2204- elif account_quadruplet_domain:
2205- accounts_criteria += account_quadruplet_domain
2206+ if account_quadruplet_domain:
2207+ acc_domains += [account_quadruplet_domain]
2208+ # "Accounts Only" Domain
2209+ account_only_domain = []
2210+ if account_destination_quadruplet_ids['account_gl_list']:
2211+ acc_ids = [a.id for a in account_destination_quadruplet_ids['account_gl_list']]
2212+ account_only_domain = [('general_account_id', 'in', acc_ids)]
2213+ if account_only_domain:
2214+ acc_domains += [account_only_domain]
2215+
2216+ # note: it's possible to have more than one domain in case several lines are grouped into a view
2217+ if len(acc_domains) == 1:
2218+ accounts_criteria += acc_domains[0]
2219+ elif len(acc_domains) == 2:
2220+ accounts_criteria += ['|'] + acc_domains[0] + acc_domains[1]
2221+ elif len(acc_domains) == 3:
2222+ accounts_criteria += ['|'] + ['|'] + acc_domains[0] + acc_domains[1] + acc_domains[2]
2223+ else:
2224+ return [('id', '=', -1)]
2225
2226 return accounts_criteria
2227 else:
2228@@ -404,6 +432,67 @@
2229
2230 return res
2231
2232+ def _get_quadruplet_sync_list(self, cr, uid, ids, field_name=None, arg=None, context=None):
2233+ tmp_quad = {}
2234+ link_ids = set()
2235+ aa_ids = set()
2236+
2237+ for line in self.browse(cr, uid, ids, fields_to_fetch=['account_quadruplet_ids'], context=context):
2238+ for quad in line.account_quadruplet_ids:
2239+ link_ids.add(quad.account_destination_link_id.id)
2240+ aa_ids.add(quad.funding_pool_id.id)
2241+ aa_ids.add(quad.cost_center_id.id)
2242+ tmp_quad.setdefault(line.id, []).append([quad.account_destination_link_id.id, quad.funding_pool_id.id, quad.cost_center_id.id])
2243+
2244+ if link_ids:
2245+ link_sdref = self.pool.get('account.destination.link').get_sd_ref(cr, uid, list(link_ids), context=context)
2246+ if aa_ids:
2247+ aa_sdref = self.pool.get('account.analytic.account').get_sd_ref(cr, uid, list(aa_ids), context=context)
2248+
2249+ ret = {}
2250+ for _id in ids:
2251+ ret[_id] = []
2252+ for quad_list in tmp_quad.get(_id, []):
2253+ ret[_id].append([link_sdref.get(quad_list[0]), aa_sdref.get(quad_list[1]), aa_sdref.get(quad_list[2])])
2254+ ret[_id] = '%s' % ret[_id]
2255+ return ret
2256+
2257+
2258+ def _set_quadruplet_sync_list(self, cr, uid, id, name, value, arg, context):
2259+ quad_obj = self.pool.get('financing.contract.account.quadruplet')
2260+
2261+ value_list = safe_eval(value)
2262+ link_ids = set()
2263+ aa_ids = set()
2264+
2265+ cr.execute('delete from financing_contract_actual_account_quadruplets where actual_line_id = %s', (id, ))
2266+ for data in value_list:
2267+ link_ids.add(data[0])
2268+ aa_ids.add(data[1])
2269+ aa_ids.add(data[2])
2270+
2271+ if link_ids:
2272+ link_sdref = self.pool.get('account.destination.link').find_sd_ref(cr, uid, list(link_ids), context=context)
2273+ if aa_ids:
2274+ aa_sdref = self.pool.get('account.analytic.account').find_sd_ref(cr, uid, list(aa_ids), context=context)
2275+
2276+ for data in value_list:
2277+ quad_id = quad_obj.search(cr, uid, [('account_destination_link_id', '=', link_sdref.get(data[0])), ('funding_pool_id', '=', aa_sdref.get(data[1])), ('cost_center_id', '=', aa_sdref.get(data[2]))], context=context)
2278+ if not quad_id:
2279+ cr.execute('''INSERT INTO financing_contract_account_quadruplet (account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id) (select
2280+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
2281+ from
2282+ financing_contract_account_quadruplet_view
2283+ where
2284+ account_destination_link_id = %s and
2285+ funding_pool_id = %s and
2286+ cost_center_id = %s
2287+ ) RETURNING id
2288+ ''', (link_sdref.get(data[0]), aa_sdref.get(data[1]), aa_sdref.get(data[2])))
2289+ quad_id = cr.fetchone()
2290+ cr.execute('insert into financing_contract_actual_account_quadruplets (actual_line_id, account_quadruplet_id) values (%s, %s)', (id, quad_id[0]))
2291+
2292+ return True
2293
2294 _columns = {
2295 'name': fields.char('Name', size=64, required=True),
2296@@ -411,7 +500,7 @@
2297 'format_id': fields.many2one('financing.contract.format', 'Format'),
2298 'is_quadruplet': fields.boolean('Input CC/FP at line level?'),
2299 'account_destination_ids': many2many_sorted('account.destination.link', 'financing_contract_actual_account_destinations', 'actual_line_id', 'account_destination_id', string='Accounts/Destinations', domain=ACCOUNT_RESTRICTED_AREA['contract_reporting_lines']),
2300- 'account_quadruplet_ids': many2many_sorted('financing.contract.account.quadruplet', 'financing_contract_actual_account_quadruplets', 'actual_line_id', 'account_quadruplet_id', string='Accounts/Destinations/Funding Pools/Cost Centres'),
2301+ 'account_quadruplet_ids': fields.many2many('financing.contract.account.quadruplet', 'financing_contract_actual_account_quadruplets', 'actual_line_id', 'account_quadruplet_id', string='Accounts/Destinations/Funding Pools/Cost Centres', order_by='account_destination_name asc, funding_pool_id asc, cost_center_id asc, id'),
2302 'parent_id': fields.many2one('financing.contract.format.line', 'Parent line'),
2303 'child_ids': fields.one2many('financing.contract.format.line', 'parent_id', 'Child lines'),
2304 'line_type': fields.selection([('view','View'),
2305@@ -431,12 +520,23 @@
2306
2307 'allocated_real': fields.function(_get_actual_amount, method=True, store=False, string="Funded - Actuals", type="float", readonly=True),
2308 'project_real': fields.function(_get_actual_amount, method=True, store=False, string="Total project - Actuals", type="float", readonly=True),
2309- 'quadruplet_update': fields.text('Internal Use Only'),
2310+ 'quadruplet_update': fields.text('Internal Use Only (deprecated - kept to manage old sync update)'),
2311+ 'quadruplet_sync_list': fields.function(_get_quadruplet_sync_list, method=True, string='Used to sync quad', type='text', fnct_inv=_set_quadruplet_sync_list),
2312 'instance_id': fields.many2one('msf.instance','Proprietary Instance'),
2313+ 'reporting_select_accounts_only': fields.boolean(string="Select Accounts Only"),
2314+ 'reporting_account_ids': fields.many2many('account.account', 'contract_format_line_account_rel', 'format_line_id', 'account_id',
2315+ string='G/L Accounts',
2316+ domain="[('type', '!=', 'view'),"
2317+ " ('is_analytic_addicted', '=', True),"
2318+ " ('active', 'in', ['t', 'f'])]",
2319+ order_by='code'),
2320 }
2321
2322+
2323+
2324 _defaults = {
2325 'is_quadruplet': False,
2326+ 'reporting_select_accounts_only': False,
2327 'line_type': 'actual',
2328 'overhead_type': 'cost_percentage',
2329 'parent_id': lambda *a: False
2330@@ -445,36 +545,58 @@
2331 _order = 'code asc'
2332
2333 # UF-2311: Calculate the quadruplet value before writing or creating the format line
2334- def calculate_quaduplet(self, vals, context):
2335+ def calculate_quadruplet(self, cr, uid, vals, context):
2336+ if context is None:
2337+ context = {}
2338+ # UC "Account/Dest." selection when...
2339+ # ...either no boxes are ticked
2340+ if 'is_quadruplet' in vals and 'reporting_select_accounts_only' in vals and \
2341+ not vals['is_quadruplet'] and not vals['reporting_select_accounts_only']:
2342+ acc_dest_selected = True
2343+ # ...or "Select Accounts Only" isn't ticked in the Donors where no quadruplets are handled
2344+ elif context.get('donor_id') and 'reporting_select_accounts_only' in vals and not vals['reporting_select_accounts_only']:
2345+ acc_dest_selected = True
2346+ else:
2347+ acc_dest_selected = False
2348+ # View Line Type = no items selected
2349 if 'line_type' in vals and vals['line_type'] == 'view':
2350 vals['allocated_amount'] = 0.0
2351 vals['project_amount'] = 0.0
2352 vals['account_destination_ids'] = [(6, 0, [])]
2353 vals['account_quadruplet_ids'] = [(6, 0, [])]
2354- elif 'is_quadruplet' in vals: # If the vals contains quadruplet value, then check if it is true or false
2355- if vals.get('is_quadruplet', False):
2356- # delete account/destinations
2357- vals['account_destination_ids'] = [(6, 0, [])]
2358- if context.get('sync_update_execution'):
2359- quads_list = []
2360- if vals.get('quadruplet_update', False):
2361- quadrup_str = vals['quadruplet_update']
2362- quads_list = map(int, quadrup_str.split(','))
2363- vals['account_quadruplet_ids'] = [(6, 0, quads_list)]
2364- else:
2365- temp = vals['account_quadruplet_ids']
2366- if temp[0]:
2367- vals['quadruplet_update'] = str(temp[0][2]).strip('[]')
2368- else:
2369- vals['account_quadruplet_ids'] = [(6, 0, [])]
2370- vals['quadruplet_update'] = '' # delete quadruplets
2371+ vals['quadruplet_update'] = ''
2372+ vals['reporting_account_ids'] = [(6, 0, [])]
2373+ vals['is_quadruplet'] = False
2374+ vals['reporting_select_accounts_only'] = False
2375+ # "Input CC/FP at line level" = quadruplets selected
2376+ elif vals.get('is_quadruplet'):
2377+ # reset the acc/dest and G/L accounts which might have been selected before ticking the box "Input CC/FP at line level"
2378+ vals['account_destination_ids'] = [(6, 0, [])]
2379+ vals['reporting_account_ids'] = [(6, 0, [])]
2380+ if context.get('sync_update_execution') and vals.get('quadruplet_update', False) and 'quadruplet_sync_list' not in vals:
2381+ # old sync update received
2382+ quadrup_str = vals['quadruplet_update']
2383+ quads_list = map(int, quadrup_str.split(','))
2384+ vals['account_quadruplet_ids'] = [(6, 0, self.pool.get('financing.contract.account.quadruplet').migrate_old_quad(cr, uid, quads_list))]
2385+ # "Select Accounts Only" = only G/L accounts selected: reset the acc/dest and quadruplets
2386+ elif vals.get('reporting_select_accounts_only'):
2387+ vals['account_destination_ids'] = [(6, 0, [])]
2388+ vals['account_quadruplet_ids'] = [(6, 0, [])]
2389+ vals['quadruplet_update'] = ''
2390+ # Accounts/Destinations selected: reset the G/L accounts and quadruplets
2391+ elif acc_dest_selected:
2392+ vals['reporting_account_ids'] = [(6, 0, [])]
2393+ vals['account_quadruplet_ids'] = [(6, 0, [])]
2394+ vals['quadruplet_update'] = ''
2395+ return True
2396+
2397
2398 def create(self, cr, uid, vals, context=None):
2399 if not context:
2400 context = {}
2401
2402 # calculate the quadruplet combination
2403- self.calculate_quaduplet(vals, context)
2404+ self.calculate_quadruplet(cr, uid, vals, context)
2405 return super(financing_contract_format_line, self).create(cr, uid, vals, context=context)
2406
2407 def write(self, cr, uid, ids, vals, context=None):
2408@@ -488,12 +610,14 @@
2409 # US-180: Check if it comes from the sync update
2410 if context.get('sync_update_execution') and vals.get('format_id', False):
2411 # US-180: and if the financing contract of the contract format does not exist, then just ignore this update
2412- exist = self.pool.get('financing.contract.contract').search(cr, uid, [('format_id', '=', vals['format_id'])])
2413+ exist = self.pool.get('financing.contract.contract').search_exists(cr, uid, [('format_id', '=', vals['format_id'])])
2414+ if not exist:
2415+ exist = self.pool.get('financing.contract.donor').search_exists(cr, uid, [('format_id', '=', vals['format_id'])])
2416 if not exist: # No contract found for this format line
2417 return True
2418
2419 # calculate the quadruplet combination
2420- self.calculate_quaduplet(vals, context)
2421+ self.calculate_quadruplet(cr, uid, vals, context)
2422 return super(financing_contract_format_line, self).write(cr, uid, ids, vals, context=context)
2423
2424 def copy_format_line(self, cr, uid, browse_source_line, destination_format_id, parent_id=None, context=None):
2425@@ -505,14 +629,37 @@
2426 'parent_id': parent_id,
2427 'line_type': browse_source_line.line_type,
2428 'account_quadruplet_ids': [(6, 0, [])],
2429+ 'reporting_select_accounts_only': browse_source_line.reporting_select_accounts_only,
2430 }
2431 account_destination_ids = [account_destination.id for account_destination in browse_source_line.account_destination_ids]
2432 format_line_vals['account_destination_ids'] = [(6, 0, account_destination_ids)]
2433+ # copy the list of "Accounts Only"
2434+ gl_account_ids = [a.id for a in browse_source_line.reporting_account_ids]
2435+ format_line_vals['reporting_account_ids'] = [(6, 0, gl_account_ids)]
2436 parent_line_id = self.pool.get('financing.contract.format.line').create(cr, uid, format_line_vals, context=context)
2437 for child_line in browse_source_line.child_ids:
2438 self.copy_format_line(cr, uid, child_line, destination_format_id, parent_line_id, context=context)
2439 return
2440
2441+ def on_change_is_quadruplet(self, cr, uid, ids, is_quadruplet, context=None):
2442+ """
2443+ Ticking "Input CC/FP at line level?" automatically unticks "Select Accounts Only"
2444+ """
2445+ res = {}
2446+ if is_quadruplet:
2447+ res['value'] = {'reporting_select_accounts_only': False, }
2448+ return res
2449+
2450+ def on_change_reporting_select_accounts_only(self, cr, uid, ids, reporting_select_accounts_only, context=None):
2451+ """
2452+ Ticking "Select Accounts Only" automatically unticks "Input CC/FP at line level?"
2453+ """
2454+ res = {}
2455+ if reporting_select_accounts_only:
2456+ res['value'] = {'is_quadruplet': False, }
2457+ return res
2458+
2459+
2460 financing_contract_format_line()
2461
2462
2463
2464=== modified file 'bin/addons/financing_contract/report/financing_contract.py'
2465--- bin/addons/financing_contract/report/financing_contract.py 2016-02-26 10:24:24 +0000
2466+++ bin/addons/financing_contract/report/financing_contract.py 2020-11-02 12:53:09 +0000
2467@@ -74,6 +74,10 @@
2468 str(quad.funding_pool_id.code),
2469 str(quad.cost_center_id.code)]),
2470 account_list, account_list_index)
2471+ elif line.reporting_account_ids:
2472+ # G/L Accounts Only selected
2473+ for account in line.reporting_account_ids:
2474+ account_list_index = add_account_list_block_item(str(account.code), account_list, account_list_index)
2475 else:
2476 # Case where we have some destination_ids
2477 for account_destination in line.account_destination_ids:
2478
2479=== modified file 'bin/addons/financing_contract/report/report_project_expenses.py'
2480--- bin/addons/financing_contract/report/report_project_expenses.py 2019-10-30 16:33:21 +0000
2481+++ bin/addons/financing_contract/report/report_project_expenses.py 2020-11-02 12:53:09 +0000
2482@@ -1,6 +1,8 @@
2483 from report import report_sxw
2484 from spreadsheet_xml.spreadsheet_xml_write import SpreadsheetReport
2485 from tools.translate import _
2486+import logging
2487+
2488 assert _ # pyflakes check
2489
2490 class report_project_expenses2(report_sxw.rml_parse):
2491@@ -17,7 +19,6 @@
2492 self.lines = {}
2493 self.totalRptCurrency = 0
2494 self.totalBookAmt = 0
2495- self.iter = []
2496 self.localcontext.update({
2497 'getLines':self.getLines,
2498 'getCostCenter':self.getCostCenter,
2499@@ -26,29 +27,12 @@
2500 'getSub1':self.getSub1,
2501 'getSub2':self.getSub2,
2502 'getLines2':self.getLines2,
2503- 'getFormula':self.getFormula,
2504 'totalRptCurrency': self.totalRptCurrency,
2505 'totalBookAmt':self.totalBookAmt,
2506 'getTotalRptCurrency': self.getTotalRptCurrency,
2507 'getTotalBookAmt': self.getTotalBookAmt,
2508 })
2509
2510- def getFormula(self):
2511- formul = ''
2512- iters = self.iter[1:]
2513- temp = self.iter[1:]
2514- tour = 1
2515- for i in temp:
2516- tour += 1
2517- nb = 0
2518- for x in iters:
2519- nb += x + 1
2520- rang = nb + 1
2521- formul += '+R[-'+str(rang)+']C'
2522- iters = self.iter[tour:]
2523-
2524- return self.totalRptCurrency
2525- return formul
2526
2527 def getTotalBookAmt(self):
2528 return self.totalBookAmt
2529@@ -100,51 +84,46 @@
2530 return []
2531 contract_obj = self.pool.get('financing.contract.contract')
2532 format_line_obj = self.pool.get('financing.contract.format.line')
2533+ logger = logging.getLogger('contract.report')
2534+
2535 contract_domain = contract_obj.get_contract_domain(self.cr, self.uid, contract, reporting_type=self.reporting_type)
2536 analytic_line_obj = self.pool.get('account.analytic.line')
2537 analytic_lines = analytic_line_obj.search(self.cr, self.uid, contract_domain, context=None)
2538
2539- # list of analytic journal_ids which are in the engagement journals
2540+ # list of analytic journal_ids which are in the engagement journals: to be added in get_contract_domain ?
2541 exclude_journal_ids = self.pool.get('account.analytic.journal').search(self.cr, self.uid, [('type','=','engagement')])
2542- exclude_line_ids = []
2543+
2544+ # gen a dict to store aji cond = reporting_line.code, reporting_line.name
2545+ line_code_name_by_cond = {}
2546+ reporting_lines_id = format_line_obj.search(self.cr, self.uid, [('format_id', '=', contract.format_id.id), ('line_type', '!=', 'view')])
2547+ for report_line in format_line_obj.browse(self.cr, self.uid, reporting_lines_id):
2548+ if report_line.is_quadruplet:
2549+ for quad in report_line.account_quadruplet_ids:
2550+ line_code_name_by_cond[(quad.account_id.id, quad.account_destination_id.id, quad.cost_center_id.id, quad.funding_pool_id.id)] = (report_line.code, report_line.name)
2551+ elif not report_line.reporting_select_accounts_only:
2552+ for triplet in report_line.account_destination_ids:
2553+ line_code_name_by_cond[(triplet.account_id.id, triplet.destination_id.id)] = (report_line.code, report_line.name)
2554+ else:
2555+ for gl_only in report_line.reporting_account_ids:
2556+ line_code_name_by_cond[gl_only.id] = (report_line.code, report_line.name)
2557+
2558+ # iterate over aji, to link each aji to its reporting_line
2559 for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2560 if analytic_line.journal_id.id in exclude_journal_ids:
2561- exclude_line_ids.append(analytic_line.id)
2562- analytic_lines = [x for x in analytic_lines if x not in exclude_line_ids]
2563-
2564- # UFTP-16: First search in the triplet in format line, then in the second block below, search in quadruplet
2565- for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2566- ids_adl = self.pool.get('account.destination.link').search(self.cr, self.uid,[('account_id', '=', analytic_line.general_account_id.id),('destination_id','=',analytic_line.destination_id.id) ])
2567- ids_fcfl = format_line_obj.search(self.cr, self.uid, [('account_destination_ids','in',ids_adl), ('format_id', '=', contract.format_id.id)])
2568- for fcfl in format_line_obj.browse(self.cr, self.uid, ids_fcfl):
2569- ana_tuple = (analytic_line, fcfl.code, fcfl.name)
2570- if lines.has_key(fcfl.code):
2571- if not ana_tuple in lines[fcfl.code]:
2572- lines[fcfl.code] += [ana_tuple]
2573- else:
2574- lines[fcfl.code] = [ana_tuple]
2575-
2576- # UFTP-16: First search in the triplet in format line, then in the second block below, search in quadruplet
2577- for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2578- # US-460: Include also the funding pool in the criteria when searching for the quadruplet of the contract line
2579- criteria_for_adl = [('account_id', '=', analytic_line.general_account_id.id),
2580- ('account_destination_id', '=', analytic_line.destination_id and analytic_line.destination_id.id or False),
2581- ('funding_pool_id', '=', analytic_line.account_id.id),
2582- ('cost_center_id', '=', analytic_line.cost_center_id and analytic_line.cost_center_id.id or False)]
2583- ids_adl = self.pool.get('financing.contract.account.quadruplet').search(self.cr, self.uid, criteria_for_adl)
2584-
2585- ids_fcfl = format_line_obj.search(self.cr, self.uid, [('account_quadruplet_ids','in',ids_adl), ('format_id', '=', contract.format_id.id)])
2586- for fcfl in format_line_obj.browse(self.cr, self.uid, ids_fcfl):
2587- ana_tuple = (analytic_line, fcfl.code, fcfl.name)
2588- if lines.has_key(fcfl.code):
2589- if not ana_tuple in lines[fcfl.code]:
2590- lines[fcfl.code] += [ana_tuple]
2591- else:
2592- lines[fcfl.code] = [ana_tuple]
2593+ continue
2594+ quad_key = (analytic_line.general_account_id.id, analytic_line.destination_id.id, analytic_line.cost_center_id.id, analytic_line.account_id.id)
2595+ if quad_key in line_code_name_by_cond:
2596+ lines.setdefault(line_code_name_by_cond[quad_key], []).append((analytic_line, line_code_name_by_cond[quad_key][0], line_code_name_by_cond[quad_key][1]))
2597+ elif quad_key[0:2] in line_code_name_by_cond:
2598+ triplet_key = quad_key[0:2]
2599+ lines.setdefault(line_code_name_by_cond[triplet_key], []).append((analytic_line, line_code_name_by_cond[triplet_key][0], line_code_name_by_cond[triplet_key][1]))
2600+ elif quad_key[0] in line_code_name_by_cond:
2601+ gl_key = quad_key[0]
2602+ lines.setdefault(line_code_name_by_cond[gl_key], []).append((analytic_line, line_code_name_by_cond[gl_key][0], line_code_name_by_cond[gl_key][1]))
2603+ else:
2604+ logger.warn('AJI id:%s, name: %s does not match any reporting lines on contract %s' % (analytic_line.id, analytic_line.entry_sequence, self.objects[0].code))
2605
2606 self.lines = lines
2607- for x in lines:
2608- self.iter.append(len(lines[x]))
2609 return lines
2610
2611
2612
2613=== modified file 'bin/addons/msf_audittrail/audittrail_invoice_data.yml'
2614--- bin/addons/msf_audittrail/audittrail_invoice_data.yml 2019-06-26 10:13:00 +0000
2615+++ bin/addons/msf_audittrail/audittrail_invoice_data.yml 2020-11-02 12:53:09 +0000
2616@@ -259,7 +259,7 @@
2617 # Create the rule
2618 fields = ['state', 'category', 'code', 'complete_name', 'cost_center_ids', 'date', 'date_start', 'name', 'parent_id',
2619 'type', 'for_fx_gain_loss', 'instance_id', 'tuple_destination_account_ids', 'description', 'destination_ids',
2620- 'dest_cc_ids', 'allow_all_cc']
2621+ 'dest_cc_ids', 'allow_all_cc', 'allow_all_cc_with_fp', 'select_accounts_only', 'fp_account_ids']
2622
2623 fields_ids = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=' ,'account.analytic.account'), ('name', 'in', fields)], context=context)
2624
2625
2626=== modified file 'bin/addons/msf_doc_import/account.py'
2627--- bin/addons/msf_doc_import/account.py 2020-06-25 08:27:13 +0000
2628+++ bin/addons/msf_doc_import/account.py 2020-11-02 12:53:09 +0000
2629@@ -180,10 +180,6 @@
2630 # Prepare some values
2631 # Do changes because of YAML tests
2632 cr = pooler.get_db(dbname).cursor()
2633- try:
2634- msf_fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2635- except ValueError:
2636- msf_fp_id = 0
2637 created = 0
2638 processed = 0
2639 errors = []
2640@@ -517,23 +513,15 @@
2641 errors.append(_('Line %s. The Cost Center %s is not compatible with the Destination %s.') %
2642 (current_line_num, line[cols['Cost Centre']], line[cols['Destination']]))
2643 continue
2644- # if the Fund. Pool used is NOT "PF" check the compatibility with the (account, dest) and the CC
2645- if r_fp != msf_fp_id:
2646- fp_fields = ['tuple_destination_account_ids', 'cost_center_ids']
2647- fp = self.pool.get('account.analytic.account').browse(cr, uid, r_fp,
2648- fields_to_fetch=fp_fields, context=context)
2649- if (account.id, r_destination) not in \
2650- [t.account_id and t.destination_id and (t.account_id.id, t.destination_id.id)
2651- for t in fp.tuple_destination_account_ids if not t.disabled]:
2652- errors.append(_('Line %s. The combination "account %s and destination %s" is not '
2653- 'compatible with the Funding Pool %s.') %
2654- (current_line_num, line[cols['G/L Account']], line[cols['Destination']],
2655- line[cols['Funding Pool']]))
2656- continue
2657- if cc.id not in [c.id for c in fp.cost_center_ids]:
2658- errors.append(_('Line %s. The Cost Center %s is not compatible with the Funding Pool %s.') %
2659- (current_line_num, line[cols['Cost Centre']], line[cols['Funding Pool']]))
2660- continue
2661+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, r_fp, account.id, r_destination, context=context):
2662+ errors.append(_('Line %s. The combination "account %s and destination %s" is not '
2663+ 'compatible with the Funding Pool %s.') %
2664+ (current_line_num, line[cols['G/L Account']], line[cols['Destination']], line[cols['Funding Pool']]))
2665+ continue
2666+ if not ad_obj.check_fp_cc_compatibility(cr, uid, r_fp, cc.id, context=context):
2667+ errors.append(_('Line %s. The Cost Center %s is not compatible with the Funding Pool %s.') %
2668+ (current_line_num, line[cols['Cost Centre']], line[cols['Funding Pool']]))
2669+ continue
2670
2671 # US-937: use period of import file
2672 if period_name.startswith('Period 16'):
2673
2674=== modified file 'bin/addons/msf_homere_interface/hr.py'
2675--- bin/addons/msf_homere_interface/hr.py 2020-09-01 08:22:26 +0000
2676+++ bin/addons/msf_homere_interface/hr.py 2020-11-02 12:53:09 +0000
2677@@ -209,21 +209,26 @@
2678 (_check_unicity, "Another employee has the same Identification No.", ['identification_id']),
2679 ]
2680
2681- def _check_employe_dest_cc_compatibility(self, cr, uid, employee_id, context=None):
2682+ def _check_employee_cc_compatibility(self, cr, uid, employee_id, context=None):
2683 """
2684- Raises an error in case the employee Destination and Cost Center are not compatible
2685+ Raises an error in case the employee "Destination and Cost Center" or "Funding Pool and Cost Center" are not compatible.
2686 """
2687 if context is None:
2688 context = {}
2689 ad_obj = self.pool.get('analytic.distribution')
2690- employee_fields = ['destination_id', 'cost_center_id', 'name_resource']
2691+ employee_fields = ['destination_id', 'cost_center_id', 'funding_pool_id', 'name_resource']
2692 employee = self.browse(cr, uid, employee_id, fields_to_fetch=employee_fields, context=context)
2693 emp_dest = employee.destination_id
2694 emp_cc = employee.cost_center_id
2695+ emp_fp = employee.funding_pool_id
2696 if emp_dest and emp_cc:
2697 if not ad_obj.check_dest_cc_compatibility(cr, uid, emp_dest.id, emp_cc.id, context=context):
2698 raise osv.except_osv(_('Error'), _('Employee %s: the Cost Center %s is not compatible with the Destination %s.') %
2699 (employee.name_resource, emp_cc.code or '', emp_dest.code or ''))
2700+ if emp_fp and emp_cc:
2701+ if not ad_obj.check_fp_cc_compatibility(cr, uid, emp_fp.id, emp_cc.id, context=context):
2702+ raise osv.except_osv(_('Error'), _('Employee %s: the Cost Center %s is not compatible with the Funding Pool %s.') %
2703+ (employee.name_resource, emp_cc.code or '', emp_fp.code or ''))
2704
2705 def create(self, cr, uid, vals, context=None):
2706 """
2707@@ -246,7 +251,7 @@
2708 if (not context.get('from', False) or context.get('from') not in ['yaml', 'import']) and not context.get('sync_update_execution', False) and not allow_edition:
2709 raise osv.except_osv(_('Error'), _('You are not allowed to create a local staff! Please use Import to create local staff.'))
2710 employee_id = super(hr_employee, self).create(cr, uid, vals, context)
2711- self._check_employe_dest_cc_compatibility(cr, uid, employee_id, context=context)
2712+ self._check_employee_cc_compatibility(cr, uid, employee_id, context=context)
2713 return employee_id
2714
2715 def write(self, cr, uid, ids, vals, context=None):
2716@@ -298,7 +303,7 @@
2717 employee_id = super(hr_employee, self).write(cr, uid, emp.id, new_vals, context)
2718 if employee_id:
2719 res.append(employee_id)
2720- self._check_employe_dest_cc_compatibility(cr, uid, emp.id, context=context)
2721+ self._check_employee_cc_compatibility(cr, uid, emp.id, context=context)
2722 return res
2723
2724 def unlink(self, cr, uid, ids, context=None):
2725@@ -328,7 +333,7 @@
2726
2727 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2728 """
2729- Change funding pool domain in order to include MSF Private fund
2730+ Adapts domain for AD fields
2731 """
2732 if not context:
2733 context = {}
2734@@ -350,36 +355,16 @@
2735 dest_field.set('domain', "[('category', '=', 'DEST'), ('type', '!=', 'view'), "
2736 "('dest_compatible_with_cc_ids', '=', cost_center_id)]")
2737 # Change FP field
2738- try:
2739- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2740- except ValueError:
2741- fp_id = 0
2742- fp_fields = form.xpath('/' + view_type + '//field[@name="funding_pool_id"]')
2743+ fp_fields = form.xpath('/' + view_type + '//field[@name="funding_pool_id"]')
2744 for field in fp_fields:
2745- field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('state', '=', 'open'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
2746+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
2747+ "('fp_compatible_with_cc_ids', '=', cost_center_id)]")
2748 view['arch'] = etree.tostring(form)
2749 return view
2750
2751 def onchange_cc(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
2752- """
2753- Update FP or CC regarding both.
2754- """
2755- # Prepare some values
2756- vals = {}
2757- if not cost_center_id or not funding_pool_id:
2758- return {}
2759- if cost_center_id and funding_pool_id:
2760- fp = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2761- try:
2762- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2763- except ValueError:
2764- fp_id = 0
2765- # Exception for MSF Private Fund
2766- if funding_pool_id == fp_id:
2767- return {}
2768- if cost_center_id not in [x.id for x in fp.cost_center_ids]:
2769- vals.update({'funding_pool_id': False})
2770- return {'value': vals}
2771+ return self.pool.get('analytic.distribution').\
2772+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
2773
2774 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
2775
2776
2777=== modified file 'bin/addons/msf_homere_interface/hr_payroll.py'
2778--- bin/addons/msf_homere_interface/hr_payroll.py 2019-05-14 15:12:35 +0000
2779+++ bin/addons/msf_homere_interface/hr_payroll.py 2020-11-02 12:53:09 +0000
2780@@ -103,7 +103,7 @@
2781 continue
2782 if line.funding_pool_id and not line.destination_id: # CASE 2/
2783 # D Check, except B check
2784- if line.cost_center_id.id not in [x.id for x in line.funding_pool_id.cost_center_ids] and line.funding_pool_id.id != fp_id:
2785+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.funding_pool_id.id, line.cost_center_id.id, context=context):
2786 res[line.id] = 'invalid'
2787 continue
2788 elif not line.funding_pool_id and line.destination_id: # CASE 3/
2789@@ -114,11 +114,12 @@
2790 continue
2791 else: # CASE 4/
2792 # C Check, except B
2793- if (line.account_id.id, line.destination_id.id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in line.funding_pool_id.tuple_destination_account_ids if not x.disabled] and line.funding_pool_id.id != fp_id:
2794+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, line.funding_pool_id.id, line.account_id.id,
2795+ line.destination_id.id, context=context):
2796 res[line.id] = 'invalid'
2797 continue
2798 # D Check, except B check
2799- if line.cost_center_id.id not in [x.id for x in line.funding_pool_id.cost_center_ids] and line.funding_pool_id.id != fp_id:
2800+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.funding_pool_id.id, line.cost_center_id.id, context=context):
2801 res[line.id] = 'invalid'
2802 continue
2803 # E Check
2804@@ -163,9 +164,9 @@
2805 if isinstance(ids, (int, long)):
2806 ids = [ids]
2807
2808- fp = [0]
2809- cc = [0]
2810- dest = [0]
2811+ fp = [-1]
2812+ cc = [-1]
2813+ dest = [-1]
2814 for ana_account in self.read(cr, uid, ids, ['category']):
2815 if ana_account['category'] == 'OC':
2816 cc.append(ana_account['id'])
2817@@ -174,8 +175,13 @@
2818 elif ana_account['category'] == 'FUNDING':
2819 fp.append(ana_account['id'])
2820 if len(fp) > 1 or len(cc) > 1 or len(dest) > 1:
2821- return self.pool.get('hr.payroll.msf').search(cr, uid, [('state', '=', 'draft'), '|', '|', ('funding_pool_id', 'in', fp), ('cost_center_id','in', cc), ('destination_id','in', dest)])
2822-
2823+ return self.pool.get('hr.payroll.msf').search(cr, uid,
2824+ [('state', '=', 'draft'),
2825+ '|', '|',
2826+ ('funding_pool_id', 'in', fp),
2827+ ('cost_center_id', 'in', cc),
2828+ ('destination_id', 'in', dest)],
2829+ order='NO_ORDER')
2830 return []
2831
2832 def _get_trigger_state_account(self, cr, uid, ids, context=None):
2833@@ -235,7 +241,12 @@
2834 store={
2835 'hr.payroll.msf': (lambda self, cr, uid, ids, c=None: ids, ['account_id', 'cost_center_id', 'funding_pool_id', 'destination_id'], 10),
2836 'account.account': (_get_trigger_state_account, ['user_type_code', 'destination_ids'], 20),
2837- 'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'cost_center_ids', 'tuple_destination_account_ids'], 20),
2838+ 'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'allow_all_cc',
2839+ 'dest_cc_ids', 'allow_all_cc_with_fp',
2840+ 'cost_center_ids', 'select_accounts_only',
2841+ 'fp_account_ids',
2842+ 'tuple_destination_account_ids'],
2843+ 20),
2844 'account.destination.link': (_get_trigger_state_dest_link, ['account_id', 'destination_id'], 30),
2845 }
2846 ),
2847@@ -255,7 +266,7 @@
2848
2849 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2850 """
2851- Change funding pool domain in order to include MSF Private fund
2852+ Adapts domain for AD fields
2853 """
2854 if not context:
2855 context = {}
2856@@ -272,13 +283,11 @@
2857 for field in fields:
2858 field.set('domain', "[('category', '=', 'OC'), ('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
2859 # Change FP field
2860- try:
2861- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2862- except ValueError:
2863- fp_id = 0
2864 fp_fields = form.xpath('//field[@name="funding_pool_id"]')
2865 for field in fp_fields:
2866- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', '&', ('cost_center_ids', '=', cost_center_id), ('tuple_destination', '=', (account_id, destination_id)), ('id', '=', %s)]" % fp_id)
2867+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
2868+ "('fp_compatible_with_cc_ids', '=', cost_center_id), "
2869+ "('fp_compatible_with_acc_dest_ids', '=', (account_id, destination_id))]")
2870 # Change Destination field
2871 dest_fields = form.xpath('//field[@name="destination_id"]')
2872 for field in dest_fields:
2873@@ -288,32 +297,8 @@
2874 return view
2875
2876 def onchange_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False):
2877- """
2878- Check given funding pool with destination
2879- """
2880- # Prepare some values
2881- res = {}
2882- # If all elements given, then search FP compatibility
2883- if destination_id and funding_pool_id and account_id:
2884- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2885- # Search MSF Private Fund element, because it's valid with all accounts
2886- try:
2887- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
2888- 'analytic_account_msf_private_funds')[1]
2889- except ValueError:
2890- fp_id = 0
2891- # Delete funding_pool_id if not valid with tuple "account_id/destination_id".
2892- # but do an exception for MSF Private FUND analytic account
2893- if (account_id, destination_id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp_line.tuple_destination_account_ids if not x.disabled] and funding_pool_id != fp_id:
2894- res = {'value': {'funding_pool_id': False}}
2895- # If no destination, do nothing
2896- elif not destination_id:
2897- res = {}
2898- # Otherway: delete FP
2899- else:
2900- res = {'value': {'funding_pool_id': False}}
2901- # If destination given, search if given
2902- return res
2903+ return self.pool.get('analytic.distribution').\
2904+ onchange_ad_destination(cr, uid, ids, destination_id=destination_id, funding_pool_id=funding_pool_id, account_id=account_id)
2905
2906 def create(self, cr, uid, vals, context=None):
2907 """
2908
2909=== modified file 'bin/addons/msf_homere_interface/hr_payroll_wizard.xml'
2910--- bin/addons/msf_homere_interface/hr_payroll_wizard.xml 2020-02-07 15:20:55 +0000
2911+++ bin/addons/msf_homere_interface/hr_payroll_wizard.xml 2020-11-02 12:53:09 +0000
2912@@ -18,7 +18,9 @@
2913 <field name="destination_id" context="{'search_default_active': 1, 'hide_inactive': 1}"
2914 domain="[('category', '=', 'DEST'), ('type', '!=', 'view'),
2915 ('dest_compatible_with_cc_ids', '=', cost_center_id)]"/>
2916- <field name="funding_pool_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
2917+ <field name="funding_pool_id" context="{'search_default_active': 1, 'hide_inactive': 1}"
2918+ domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'),
2919+ ('fp_compatible_with_cc_ids', '=', cost_center_id)]"/>
2920 </group>
2921 <newline/>
2922 <field name="free1_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
2923
2924=== modified file 'bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py'
2925--- bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py 2015-07-22 09:03:35 +0000
2926+++ bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py 2020-11-02 12:53:09 +0000
2927@@ -40,7 +40,7 @@
2928
2929 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2930 """
2931- Change funding pool domain in order to include MSF Private fund
2932+ Computes the domain for the Cost Center field
2933 """
2934 if not context:
2935 context = {}
2936@@ -56,41 +56,13 @@
2937 fields = form.xpath('//field[@name="cost_center_id"]')
2938 for field in fields:
2939 field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
2940- # Change FP field
2941- try:
2942- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2943- except ValueError:
2944- fp_id = 0
2945- fp_fields = form.xpath('//field[@name="funding_pool_id"]')
2946- # Do not use line with account_id, because of NO ACCOUNT_ID PRESENCE!
2947- for field in fp_fields:
2948- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
2949- # NO NEED TO CHANGE DESTINATION_ID FIELD because NO ACCOUNT_ID PRESENCE!
2950 # Apply changes
2951 view['arch'] = etree.tostring(form)
2952 return view
2953
2954 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
2955- """
2956- Check given cost_center with funding pool
2957- """
2958- # Prepare some values
2959- res = {}
2960- if cost_center_id and funding_pool_id:
2961- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2962- # Search MSF Private Fund element, because it's valid with all accounts
2963- try:
2964- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
2965- 'analytic_account_msf_private_funds')[1]
2966- except ValueError:
2967- fp_id = 0
2968- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and funding_pool_id != fp_id:
2969- res = {'value': {'funding_pool_id': False}}
2970- elif not cost_center_id:
2971- res = {}
2972- else:
2973- res = {'value': {'funding_pool_id': False}}
2974- return res
2975+ return self.pool.get('analytic.distribution').\
2976+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
2977
2978 def button_validate(self, cr, uid ,ids, context=None):
2979 """
2980
2981=== modified file 'bin/addons/msf_instance/account_target_costcenter.py'
2982--- bin/addons/msf_instance/account_target_costcenter.py 2019-05-14 07:28:06 +0000
2983+++ bin/addons/msf_instance/account_target_costcenter.py 2020-11-02 12:53:09 +0000
2984@@ -27,8 +27,8 @@
2985 _trace = True
2986
2987 _columns = {
2988- 'instance_id': fields.many2one('msf.instance', 'Instance', required=True),
2989- 'cost_center_id': fields.many2one('account.analytic.account', 'Code', domain=[('category', '=', 'OC')], required=True),
2990+ 'instance_id': fields.many2one('msf.instance', 'Instance', required=True, select=1),
2991+ 'cost_center_id': fields.many2one('account.analytic.account', 'Code', domain=[('category', '=', 'OC')], required=True, select=1),
2992 'cost_center_name': fields.related('cost_center_id', 'name', string="Name", readonly=True, type="text"),
2993 'is_target': fields.boolean('Is target'),
2994 'is_top_cost_center': fields.boolean('Top cost centre for budget consolidation'),
2995
2996=== modified file 'bin/addons/msf_profile/data/patches.xml'
2997--- bin/addons/msf_profile/data/patches.xml 2020-10-09 13:44:47 +0000
2998+++ bin/addons/msf_profile/data/patches.xml 2020-11-02 12:53:09 +0000
2999@@ -610,5 +610,9 @@
3000 <field name="method">us_2725_uf_write_date_on_products</field>
3001 </record>
3002
3003+ <record id="us_7243_migrate_contract_quad" model="patch.scripts">
3004+ <field name="method">us_7243_migrate_contract_quad</field>
3005+ </record>
3006+
3007 </data>
3008 </openerp>
3009
3010=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
3011--- bin/addons/msf_profile/i18n/fr_MF.po 2020-10-28 17:16:11 +0000
3012+++ bin/addons/msf_profile/i18n/fr_MF.po 2020-11-02 12:53:09 +0000
3013@@ -3460,7 +3460,7 @@
3014 #: view:financing.contract.account.quadruplet:0
3015 #: view:financing.contract.contract:0
3016 msgid "Account/Destination/Funding Pool/Cost Centre"
3017-msgstr "Account/Destination/Funding Pool/Cost Centre"
3018+msgstr "Compte/Destination/Funding Pool/Centre de Coût"
3019
3020 #. module: sync_client
3021 #: field:sync.monitor,data_push_send:0
3022@@ -8938,7 +8938,7 @@
3023 #. module: financing_contract
3024 #: view:financing.contract.contract:0
3025 msgid "Remove all couples"
3026-msgstr "Remove all couples"
3027+msgstr "Supprimer toutes les paires"
3028
3029 #. modules: purchase, tender_flow, msf_outgoing, stock
3030 #: view:stock.picking:0
3031@@ -15941,7 +15941,7 @@
3032 #. module: financing_contract
3033 #: view:financing.contract.contract:0
3034 msgid "Remove all quads"
3035-msgstr "Remove all quads"
3036+msgstr "Supprimer tous les quadruplets"
3037
3038 #. modules: tender_flow, process, account_hq_entries, account_override, procurement_cycle, return_claim, finance, sync_client, account_mcdb, procurement_request, product_asset, board, stock_override, msf_button_access_rights, analytic_distribution, msf_homere_interface, hr, consumption_calculation, register_accounting, kit, base, procurement_report, threshold_value, purchase, account, msf_outgoing, resource, stock_move_tracking, msf_partner, procurement_auto, msf_field_access_rights, sale, transport_mgmt, procurement, sourcing, msf_audittrail, stock
3039 #: view:account.account:0
3040@@ -16349,8 +16349,14 @@
3041
3042 #. module: analytic_distribution
3043 #: view:account.analytic.account:0
3044-msgid "Remove all"
3045-msgstr "Tout supprimer"
3046+msgid "Remove all accounts/destinations"
3047+msgstr "Supprimer tous les comptes/destinations"
3048+
3049+#. modules: analytic_distribution, financing_contract
3050+#: view:account.analytic.account:0
3051+#: view:financing.contract.contract:0
3052+msgid "Remove all accounts"
3053+msgstr "Supprimer tous les comptes"
3054
3055 #. module: vertical_integration
3056 #: code:addons/vertical_integration/report/hq_report_oca.py:176
3057@@ -28883,8 +28889,9 @@
3058 msgid "This view can be used by accountants in order to quickly record entries in OpenERP. If you want to record a supplier invoice, start by recording the line of the expense account. OpenERP will propose to you automatically the Tax related to this account and the counterpart \"Account Payable\"."
3059 msgstr "Cette vue peut être utilisée par des comptables afin d'enregistrer rapidement des écritures comptables dans OpenERP. Si vous voulez enregistrer une facture fournisseur, commencez par enregistrer la ligne du compte de charge. OpenERP vous proposera automatiquement la taxe afférente à ce compte et la contrepartie \"Compte fournisseur\"."
3060
3061-#. module: financing_contract
3062+#. modules: financing_contract, analytic_distribution
3063 #: field:financing.contract.format.line,account_destination_ids:0
3064+#: view:account.analytic.account:0
3065 msgid "Accounts/Destinations"
3066 msgstr "Comptes/Destinations"
3067
3068@@ -53406,8 +53413,8 @@
3069
3070 #. module: financing_contract
3071 #: field:financing.contract.format.line,quadruplet_update:0
3072-msgid "Internal Use Only"
3073-msgstr "Internal Use Only"
3074+msgid "Internal Use Only (deprecated - kept to manage old sync update)"
3075+msgstr "Usage Interne Uniquement (déprécié - conservé pour gérer les anciennes mises à jour via synch.)"
3076
3077 #. modules: base_setup, unifield_setup, base
3078 #: selection:base.setup.company,country_id:0
3079@@ -60385,6 +60392,62 @@
3080 msgid "G/L Account"
3081 msgstr "Compte Grand Livre"
3082
3083+#. modules: analytic_distribution, account, analytic_override, financing_contract
3084+#: view:account.analytic.account:0
3085+#: view:account.account:0
3086+#: field:account.analytic.account,fp_account_ids:0
3087+#: view:financing.contract.contract:0
3088+#: field:financing.contract.format.line,reporting_account_ids:0
3089+#: view:financing.contract.donor:0
3090+msgid "G/L Accounts"
3091+msgstr "Comptes Grand Livre"
3092+
3093+#. module: analytic_distribution
3094+#: report:funding.pool:0
3095+msgid "G/L Accounts:"
3096+msgstr "Comptes Grand Livre :"
3097+
3098+#. module: analytic_override
3099+#: help:account.analytic.account,fp_account_ids:0
3100+msgid "G/L accounts linked to the Funding Pool"
3101+msgstr "Comptes Grand Livre liés au Funding Pool"
3102+
3103+#. modules: analytic_override, financing_contract
3104+#: field:account.analytic.account,select_accounts_only:0
3105+#: field:financing.contract.format.line,reporting_select_accounts_only:0
3106+msgid "Select Accounts Only"
3107+msgstr "Sélectionner des Comptes Uniquement"
3108+
3109+#. module: account_override
3110+#: field:account.account,selected_in_fp:0
3111+msgid "Selected in Funding Pool"
3112+msgstr "Sélectionné dans le Funding Pool"
3113+
3114+#. module: account_override
3115+#: field:account.account,selectable_in_contract:0
3116+msgid "Selectable in Contract"
3117+msgstr "Sélectionnable dans le Contrat"
3118+
3119+#. module: account_override
3120+#: field:account.account,selected_in_contract:0
3121+msgid "Selected in Contract or Donor"
3122+msgstr "Sélectionné dans le Contrat ou le Bailleur"
3123+
3124+#. module: financing_contract
3125+#: sql_constraint:financing.contract.account.quadruplet:0
3126+msgid "not unique!"
3127+msgstr "non unique !"
3128+
3129+#. module: financing_contract
3130+#: field:financing.contract.format.line,quadruplet_sync_list:0
3131+msgid "Used to sync quad"
3132+msgstr "Utilisé pour synch. les quadruplets"
3133+
3134+#. module: financing_contract
3135+#: field:financing.contract.contract,quad_gen_date:0
3136+msgid "Date of last generation of quad"
3137+msgstr "Date de la dernière génération des quadruplets"
3138+
3139 #. module: purchase_override
3140 #: view:purchase.order.cancel.wizard:0
3141 msgid "Please confirm that you want to cancel all non-confirmed lines of this PO"
3142@@ -61535,10 +61598,10 @@
3143 msgstr "Le total hors-taxe"
3144
3145 #. module: register_accounting
3146-#: code:addons/register_accounting/wizard/wizard_cash_return.py:800
3147+#: code:addons/register_accounting/wizard/wizard_cash_return.py:824
3148 #, python-format
3149-msgid "All advance lines with account that depends on analytic distribution must have an allocation."
3150-msgstr "All advance lines with account that depends on analytic distribution must have an allocation."
3151+msgid "All advance lines with an account depending on an analytic distribution must have a valid allocation."
3152+msgstr "Toutes les lignes d'avance dont le compte dépend d'une distribution analytique doivent avoir une allocation valide."
3153
3154 #. module: stock
3155 #: field:product.category,property_stock_account_output_categ:0
3156@@ -69077,7 +69140,6 @@
3157 msgstr "Canceled End"
3158
3159 #. modules: financing_contract, analytic_distribution
3160-#: field:financing.contract.account.quadruplet,account_destination_id:0
3161 #: field:account.analytic.account,tuple_destination_account_ids:0
3162 #: view:account.destination.link:0
3163 #: view:account.destination.link:0
3164@@ -94241,6 +94303,7 @@
3165 #: view:account.destination.summary:0
3166 #: code:addons/stock_override/report/report_stock_move.py:759
3167 #: report:addons/account/report/invoice_excel_export.mako:75
3168+#: field:financing.contract.account.quadruplet,account_destination_id:0
3169 #, python-format
3170 msgid "Destination"
3171 msgstr "Destination"
3172@@ -104790,7 +104853,20 @@
3173 msgstr "Filtre non mis en oeuvre sur les Destinations."
3174
3175 #. module: analytic_override
3176+#: code:addons/analytic_override/analytic_account.py:307
3177+#, python-format
3178+msgid "Filter not implemented on Funding Pools."
3179+msgstr "Filtre non mis en oeuvre sur les Funding Pools."
3180+
3181+#. module: analytic_override
3182+#: code:addons/analytic_override/analytic_account.py:312
3183+#, python-format
3184+msgid "Filter only compatible with a normal-type Cost Center."
3185+msgstr "Filtre compatible uniquement avec un Centre de Coût de type normal."
3186+
3187+#. module: analytic_override
3188 #: field:account.analytic.account,allow_all_cc:0
3189+#: field:account.analytic.account,allow_all_cc_with_fp:0
3190 msgid "Allow all Cost Centers"
3191 msgstr "Autoriser tous les Centres de Coût"
3192
3193@@ -104806,17 +104882,39 @@
3194 msgstr "Destinations compatibles avec le Centre de Coût"
3195
3196 #. module: analytic_override
3197+#: field:account.analytic.account,fp_compatible_with_cc_ids:0
3198+msgid "Funding Pools compatible with the Cost Center"
3199+msgstr "Funding Pools compatibles avec le Centre de Coût"
3200+
3201+#. module: analytic_override
3202+#: field:account.analytic.account,fp_compatible_with_acc_dest_ids:0
3203+msgid "Funding Pools compatible with the Account/Destination combination"
3204+msgstr "Funding Pools compatibles avec la combinaison Compte/Destination"
3205+
3206+#. module: analytic_override
3207 #: code:addons/analytic_override/analytic_account.py:347
3208 #, python-format
3209 msgid "Please remove the Cost Centers linked to the Destination before ticking this box."
3210 msgstr "Veuillez supprimer les Centres de Coût liés à la Destination avant de cocher cette case."
3211
3212+#. module: analytic_override
3213+#: code:addons/analytic_override/analytic_account.py:347
3214+#, python-format
3215+msgid "Please remove the Cost Centers linked to the Funding Pool before ticking this box."
3216+msgstr "Veuillez supprimer les Centres de Coût liés au Funding Pool avant de cocher cette case."
3217+
3218 #. module: account_corrections
3219 #: code:addons/account_corrections/wizard/analytic_distribution_wizard.py:246
3220 #, python-format
3221 msgid "The Cost Center %s is not compatible with the Destination %s."
3222 msgstr "Le Centre de Coût %s n'est pas compatible avec la Destination %s."
3223
3224+#. module: account_corrections
3225+#: code:addons/account_corrections/wizard/analytic_distribution_wizard.py:274
3226+#, python-format
3227+msgid "The Cost Center %s is not compatible with the Funding Pool %s."
3228+msgstr "Le Centre de Coût %s n'est pas compatible avec le Funding Pool %s."
3229+
3230 #. module: msf_doc_import
3231 #: code:addons/msf_doc_import/account.py:490
3232 #, python-format
3233@@ -105166,6 +105264,12 @@
3234 msgid "Employee %s: the Cost Center %s is not compatible with the Destination %s."
3235 msgstr "Employé %s : le Centre de Coût %s n'est pas compatible avec la Destination %s."
3236
3237+#. module: msf_homere_interface
3238+#: code:addons/msf_homere_interface/hr.py:229
3239+#, python-format
3240+msgid "Employee %s: the Cost Center %s is not compatible with the Funding Pool %s."
3241+msgstr "Employé %s : le Centre de Coût %s n'est pas compatible avec le Funding Pool %s."
3242+
3243 #. module: msf_supply_doc_export
3244 #: report:po.follow.up_rml:0
3245 msgid "Status:"
3246@@ -109907,6 +110011,11 @@
3247 msgid "Missions where the CC is added to"
3248 msgstr "Missions dans lesquelles le CC est ajouté"
3249
3250+#. module: analytic_override
3251+#: field:account.analytic.account,cc_instance_ids:0
3252+msgid "Instances where the CC is added to"
3253+msgstr "Instances dans lesquelles le CC est ajouté"
3254+
3255 #. module: sync_client
3256 #: field:sync_client.survey.user,nb_displayed:0
3257 msgid "# Display"
3258
3259=== modified file 'bin/addons/msf_profile/msf_profile.py'
3260--- bin/addons/msf_profile/msf_profile.py 2020-10-09 13:44:47 +0000
3261+++ bin/addons/msf_profile/msf_profile.py 2020-11-02 12:53:09 +0000
3262@@ -53,6 +53,25 @@
3263 }
3264
3265 # UF19.0
3266+ def us_7243_migrate_contract_quad(self, cr, uid, *a, **b):
3267+ quad_obj = self.pool.get('financing.contract.account.quadruplet')
3268+ if not cr.table_exists('financing_contract_actual_account_quadruplets_old'):
3269+ cr.execute("create table financing_contract_actual_account_quadruplets_old as (select * from financing_contract_actual_account_quadruplets)")
3270+ already_migrated = {}
3271+ cr.execute('truncate financing_contract_actual_account_quadruplets')
3272+ cr.execute('select actual_line_id, account_quadruplet_id from financing_contract_actual_account_quadruplets_old')
3273+ nb_mig = 0
3274+ for x in cr.fetchall():
3275+ if x[1] not in already_migrated:
3276+ new_id = quad_obj.migrate_old_quad(cr, uid, [x[1]])
3277+ already_migrated[x[1]] = new_id and new_id[0]
3278+ if already_migrated.get(x[1]):
3279+ nb_mig += 1
3280+ cr.execute('insert into financing_contract_actual_account_quadruplets (actual_line_id, account_quadruplet_id) values (%s, %s)', (x[0], already_migrated[x[1]]))
3281+
3282+ self._logger.warn('%d quad migrated' % (nb_mig,))
3283+ return True
3284+
3285 def us_2725_uf_write_date_on_products(self, cr, uid, *a, **b):
3286 '''
3287 Set the uf_write_date of products which don't have one to the date of creation
3288@@ -3910,10 +3929,7 @@
3289 except Exception as e:
3290 err_msg = 'Error with the patch scripts %s.%s :: %s' % (ps['model'], ps['method'], e)
3291 self._logger.error(err_msg)
3292- raise osv.except_osv(
3293- 'Error',
3294- err_msg,
3295- )
3296+ raise
3297
3298 patch_scripts()
3299
3300
3301=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
3302--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-09-01 08:22:26 +0000
3303+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-11-02 12:53:09 +0000
3304@@ -26,7 +26,7 @@
3305 msf_sync_data_server.analytical_journal_project,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional,"[('instance_id.level', '=', 'project'),('code','!=','ENGI')]","['code', 'name', 'type','instance_id/id']",MISSION,account.analytic.journal,,Analytical Journal (Project),Valid,,121
3306 msf_sync_data_server.analytical_journal,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,[],"['code', 'name', 'type','instance_id/id']",OC,account.analytic.journal,instance_id,Analytical Journal,Valid,,122
3307 msf_sync_data_server.link_accounts_destination,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,[],"['account_id/id', 'destination_id/id', 'disabled']",OC,account.destination.link,,Link accounts Destination,Valid,,123
3308-msf_sync_data_server.funding_pool_to_coordo,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,"[('category' , '=' , 'FUNDING')]","['category', 'code', 'cost_center_ids/id', 'date', 'date_start', 'description', 'instance_id/id', 'name', 'tuple_destination_account_ids/id', 'type']",HQ + MISSION,account.analytic.account,instance_id,Funding Pool linked to a special coordo,Valid,,124
3309+msf_sync_data_server.funding_pool_to_coordo,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,"[('category' , '=' , 'FUNDING')]","['category', 'code', 'cost_center_ids/id', 'date', 'date_start', 'description', 'instance_id/id', 'name', 'tuple_destination_account_ids/id', 'type', 'allow_all_cc_with_fp', 'select_accounts_only', 'fp_account_ids/id']",HQ + MISSION,account.analytic.account,instance_id,Funding Pool linked to a special coordo,Valid,,124
3310 msf_sync_data_server.fp_tree0,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('category', 'in', ['FREE1', 'FREE2', 'DEST']),('parent_id', '!=', '')]","['parent_id/id', 'instance_id/id']",OC,account.analytic.account,instance_id,"DestF1F2 tree (only F1, F2 and Dest)",Valid,,126
3311 msf_sync_data_server.fp_tree,FALSE,TRUE,TRUE,FALSE,bidirectional,Bidirectional,"[('category', 'in', ['FREE1', 'FREE2', 'DEST']),('parent_id', '!=', '')]",['parent_id/id'],MISSION,account.analytic.account,,"Analytic account tree (only F1, F2 and Dest) – Coordo/Projects only",Valid,,127
3312 msf_sync_data_server.currency_rate,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('rate' , '!=' , ''),('currency_id', '!=', ''), ('currency_id', 'in', ('res.currency', 'id', [('active', 'in', ['t', 'f']), ('currency_table_id', '=', '')]))]","['currency_id/id', 'name', 'rate']",OC,res.currency.rate,,Currency Rate,Valid,,128
3313@@ -103,12 +103,12 @@
3314 msf_sync_data_server.mission_stock_report_line_OC,TRUE,TRUE,FALSE,FALSE,bidirectional,Up,"[('mission_report_id.full_view', '=', False), ('mission_report_id.local_report', '=', True), ('international_status','in',['ITC', 'UniData', 'ESC', 'HQ'])]","['central_qty', 'central_val', 'cross_qty', 'cross_val', 'cu_qty', 'cu_val', 'in_pipe_coor_qty', 'in_pipe_coor_val', 'in_pipe_qty', 'in_pipe_val', 'internal_qty', 'internal_val', 'mission_report_id/id', 'product_id/id', 'secondary_qty', 'secondary_val', 'stock_qty', 'stock_val', 'xmlid_code', 'product_state', 'product_active', 'state_ud', 'international_status_code', 'product_amc', 'product_consumption']",OC,stock.mission.report.line,,Mission Stock Report Line OC,Valid,,442
3315 msf_sync_data_server.financing_contract_formats_fc,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,[],"['cost_center_ids/id', 'eligibility_from_date', 'eligibility_to_date', 'format_name', 'overhead_percentage', 'overhead_type', 'reporting_type', 'hidden_instance_id/id']",HQ + MISSION,financing.contract.format,hidden_instance_id,Financing Contract Formats FC,Valid,,450
3316 msf_sync_data_server.financing_contract_formats,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional,"[('hidden_instance_id','=',False)]","['hidden_instance_id/id','cost_center_ids/id', 'eligibility_from_date', 'eligibility_to_date', 'format_name', 'overhead_percentage', 'overhead_type', 'reporting_type']",HQ + MISSION,financing.contract.format,,Financing Contract Formats,Valid,,451
3317-msf_sync_data_server.financing_contract_format_lines,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional,"[('instance_id', '=', False)]","['account_destination_ids/id', 'instance_id/id','allocated_budget_value', 'allocated_real_value', 'code', 'format_id/id', 'line_type', 'name', 'overhead_percentage', 'overhead_type', 'project_budget_value', 'project_real_value', 'is_quadruplet','quadruplet_update']",HQ + MISSION,financing.contract.format.line,,Financing Contract Format Lines,Valid,,452
3318+msf_sync_data_server.financing_contract_format_lines,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional,"[('instance_id', '=', False)]","['account_destination_ids/id', 'instance_id/id','allocated_budget_value', 'allocated_real_value', 'code', 'format_id/id', 'line_type', 'name', 'overhead_percentage', 'overhead_type', 'project_budget_value', 'project_real_value', 'is_quadruplet', 'quadruplet_sync_list', 'reporting_select_accounts_only', 'reporting_account_ids/id']",HQ + MISSION,financing.contract.format.line,,Financing Contract Format Lines,Valid,,452
3319 msf_sync_data_server.financing_contract_format_lines_tree,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional,"[('parent_id', '!=', ''), ('instance_id', '=', False)]",['parent_id/id'],HQ + MISSION,financing.contract.format.line,,Financing Contract Format Lines Tree,Valid,,453
3320 msf_sync_data_server.donors,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional,[],"['active', 'code', 'format_id/id', 'name', 'reporting_currency/id']",HQ + MISSION,financing.contract.donor,,Donors,Valid,,454
3321 msf_sync_data_server.financing_contract,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional-Private,[],"['code', 'currency_table_id/id', 'donor_grant_reference', 'donor_id/id', 'eligibility_from_date', 'eligibility_to_date', 'format_id/id', 'grant_amount', 'hard_closed_date', 'hq_grant_reference', 'name', 'notes', 'open_date', 'reporting_currency/id', 'soft_closed_date', 'state', 'instance_id/id']",HQ + MISSION,financing.contract.contract,instance_id,Financing Contract,Valid,,455
3322 msf_sync_data_server.financing_contract_fp_line,TRUE,TRUE,TRUE,TRUE,bidirectional,Bidirectional-Private,[],"['contract_id/id', 'funded', 'funding_pool_id/id', 'total_project', 'instance_id/id']",HQ + MISSION,financing.contract.funding.pool.line,instance_id,Financing Contract FP Line,Valid,,456
3323-msf_sync_data_server.financing_contract_format_lines_fc,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,[],"['account_destination_ids/id', 'allocated_budget_value', 'allocated_real_value', 'code', 'format_id/id', 'instance_id/id', 'line_type', 'name', 'overhead_percentage', 'overhead_type', 'project_budget_value', 'project_real_value', 'is_quadruplet','quadruplet_update']",HQ + MISSION,financing.contract.format.line,instance_id,Financing Contract Format Lines FC,Valid,,457
3324+msf_sync_data_server.financing_contract_format_lines_fc,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,[],"['account_destination_ids/id', 'allocated_budget_value', 'allocated_real_value', 'code', 'format_id/id', 'instance_id/id', 'line_type', 'name', 'overhead_percentage', 'overhead_type', 'project_budget_value', 'project_real_value', 'is_quadruplet', 'quadruplet_sync_list', 'reporting_select_accounts_only', 'reporting_account_ids/id']",HQ + MISSION,financing.contract.format.line,instance_id,Financing Contract Format Lines FC,Valid,,457
3325 msf_sync_data_server.financing_contract_format_lines_tree_fc,TRUE,TRUE,FALSE,TRUE,bidirectional,Bidirectional-Private,"[('parent_id','!=','')]","['parent_id/id', 'instance_id/id']",HQ + MISSION,financing.contract.format.line,instance_id,Financing Contract Format Lines Tree FC,Valid,,458
3326 msf_sync_data_server.country,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['code', 'name']",OC,res.country,,Country,Valid,,500
3327 msf_sync_data_server.state,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['code', 'country_id/id', 'name']",OC,res.country.state,,State,Valid,,501
3328
3329=== modified file 'bin/addons/register_accounting/wizard/wizard_cash_return.py'
3330--- bin/addons/register_accounting/wizard/wizard_cash_return.py 2020-02-04 13:20:04 +0000
3331+++ bin/addons/register_accounting/wizard/wizard_cash_return.py 2020-11-02 12:53:09 +0000
3332@@ -820,7 +820,9 @@
3333 # check if any line with an analytic-a-holic account missing the distribution_id value
3334 for st_line in wizard.advance_line_ids:
3335 if st_line.account_id.is_analytic_addicted and st_line.analytic_distribution_state != 'valid':
3336- raise osv.except_osv(_('Warning'), _('All advance lines with account that depends on analytic distribution must have an allocation.'))
3337+ raise osv.except_osv(_('Warning'),
3338+ _('All advance lines with an account depending on an analytic distribution '
3339+ 'must have a valid allocation.'))
3340
3341 # Do computation of total_amount of advance return lines
3342 self.compute_total_amount(cr, uid, ids, context=context)

Subscribers

People subscribed via source and target branches