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

Proposed by jftempo
Status: Merged
Merged at revision: 5874
Proposed branch: lp:~julie-w/unifield-server/US-7245
Merge into: lp:unifield-server
Diff against target: 3294 lines (+1320/-692)
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 (+24/-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 (+212/-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 (+41/-8)
bin/addons/financing_contract/format.py (+5/-27)
bin/addons/financing_contract/format_line.py (+210/-80)
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 (+120/-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-7245
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+393178@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:07 +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 or a Financing Contract
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_contract'):
16+ view = False
17+ module = 'account'
18+ if view_type == 'search':
19+ search_view_name = context.get('from_contract') 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:07 +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 -->
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 -->
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:07 +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:07 +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:07 +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:07 +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:07 +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:07 +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:07 +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 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:
447+ 'selected_in_contract': fields.function(_get_selected_in_contract, string='Selected in Contract', 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:07 +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:07 +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:07 +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:07 +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:07 +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:07 +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:07 +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:07 +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,23 @@
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+ if ad_obj.check_dest_cc_compatibility(cr, uid, account_id, aline.cost_center_id and aline.cost_center_id.id or False,
874+ context=context) and \
875+ account_id in [x.id for x in aline.general_account_id.destination_ids] and \
876+ ad_obj.check_fp_acc_dest_compatibility(cr, uid, aline.account_id.id, aline.general_account_id.id,
877+ account_id, context=context):
878 res.append(aline.id)
879 else:
880 # Case of FREE1 and FREE2 lines
881@@ -538,23 +529,17 @@
882 res.append((id, entry_sequence, _('CC/DEST')))
883 return False
884
885- # check funding pool (expect for MSF Private Fund)
886- if not new_fp_id == msf_pf_id: # all OK for MSF Private Fund
887- # - cost center and funding pool compatibility
888- cc_ids = [cc.id for cc in new_fp_br.cost_center_ids]
889- if not new_cc_id in cc_ids:
890- # not compatible with CC
891- res.append((id, entry_sequence, _('CC')))
892- return False
893+ # - cost center and funding pool compatibility
894+ if not ad_obj.check_fp_cc_compatibility(cr, uid, new_fp_id, new_cc_id, context=context):
895+ # not compatible with CC
896+ res.append((id, entry_sequence, _('CC')))
897+ return False
898
899- # - destination / account
900- acc_dest = (general_account_br.id, new_dest_id)
901- if acc_dest not in [x.account_id and x.destination_id and \
902- (x.account_id.id, x.destination_id.id) \
903- for x in new_fp_br.tuple_destination_account_ids if not x.disabled]:
904- # not compatible with dest/account
905- res.append((id, entry_sequence, _('account/dest')))
906- return False
907+ # - destination / account
908+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, new_fp_id, general_account_br.id, new_dest_id, context=context):
909+ # not compatible with account/dest
910+ res.append((id, entry_sequence, _('account/dest')))
911+ return False
912
913 # check active date
914 if not check_date(new_dest_br, posting_date):
915
916=== modified file 'bin/addons/analytic_distribution/report/funding_pool.py'
917--- bin/addons/analytic_distribution/report/funding_pool.py 2019-04-03 13:47:21 +0000
918+++ bin/addons/analytic_distribution/report/funding_pool.py 2020-11-02 12:53:07 +0000
919@@ -29,6 +29,7 @@
920 self.localcontext.update({
921 'locale': locale,
922 'today': self.today,
923+ 'all_cc': lambda f: self.pool.get('account.analytic.account').get_cc_linked_to_fp(cr, uid, f, context=context),
924 })
925
926 def today(self):
927
928=== modified file 'bin/addons/analytic_distribution/report/funding_pool.rml'
929--- bin/addons/analytic_distribution/report/funding_pool.rml 2019-04-17 10:00:14 +0000
930+++ bin/addons/analytic_distribution/report/funding_pool.rml 2020-11-02 12:53:07 +0000
931@@ -206,7 +206,7 @@
932 </td>
933 </tr>
934 <tr>
935- <para style="P17">[[repeatIn(o.cost_center_ids,'line')]]</para>
936+ <para style="P17">[[repeatIn(all_cc(o.id), 'line')]]</para>
937 <td>
938 <para style="P7">[[ line.code or '' ]]</para>
939 </td>
940@@ -222,9 +222,11 @@
941 <para style="P8">
942 <font color="white"> </font>
943 </para>
944- <para style="P6">Account/Destination:</para>
945+ <para style="P6">[[ o.select_accounts_only and translate("G/L Accounts:") or translate("Account/Destination:") ]]</para>
946
947+ <!-- Account/Destination -->
948 <blockTable repeatRows="1" style="Table4" colWidths="95.0,165.0,95.0,165.0">
949+ [[ not o.select_accounts_only or removeParentNode('blockTable') ]]
950 <tr>
951 <td>
952 <para style="P8">Account code</para>
953@@ -257,6 +259,28 @@
954 </tr>
955 </blockTable>
956
957+ <!-- G/L Accounts -->
958+ <blockTable repeatRows="1" style="Table4" colWidths="260.0,260.0">
959+ [[ o.select_accounts_only or removeParentNode('blockTable') ]]
960+ <tr>
961+ <td>
962+ <para style="P8">Account code</para>
963+ </td>
964+ <td>
965+ <para style="P8">Account name</para>
966+ </td>
967+ </tr>
968+ <tr>
969+ [[ repeatIn(o.fp_account_ids, 'acc') ]]
970+ <td>
971+ <para style="P7">[[ acc.code ]]</para>
972+ </td>
973+ <td>
974+ <para style="P7">[[ acc.name ]]</para>
975+ </td>
976+ </tr>
977+ </blockTable>
978+
979 <para style="P5">
980 <font color="white"> </font>
981 </para>
982
983=== modified file 'bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py'
984--- bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2020-07-16 15:42:42 +0000
985+++ bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2020-11-02 12:53:07 +0000
986@@ -237,12 +237,17 @@
987 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)) \
988 or (context.get('from_model', False) and isinstance(context.get('from_model'), int)) \
989 or (context.get('from_move', False) and isinstance(context.get('from_move'), int)) \
990- or (context.get('from_cash_return', False) and isinstance(context.get('from_cash_return'), int)):
991- # Filter is only on cost_center and MSF Private Fund on invoice header
992- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), ('hide_closed_fp', '=', True), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
993+ or (context.get('from_cash_return', False) and isinstance(context.get('from_cash_return'), int))\
994+ or (context.get('direct_invoice_id', False) and isinstance(context.get('direct_invoice_id'), int)):
995+ # Filter is only on cost_centers on invoice header
996+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
997+ "('hide_closed_fp', '=', True), ('fp_compatible_with_cc_ids', '=', cost_center_id)]")
998 else:
999 # Add account_id constraints for invoice lines
1000- 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)
1001+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
1002+ "('hide_closed_fp', '=', True), "
1003+ "('fp_compatible_with_cc_ids', '=', cost_center_id), "
1004+ "('fp_compatible_with_acc_dest_ids', '=', (parent.account_id, destination_id))]")
1005 # Change Destination field
1006 dest_fields = tree.xpath('/tree/field[@name="destination_id"]')
1007 for field in dest_fields:
1008@@ -404,60 +409,18 @@
1009 }
1010
1011 def onchange_destination(self, cr, uid, ids, destination_id=False, analytic_id=False, account_id=False):
1012- """
1013- Check given funding pool with destination
1014- """
1015- # Prepare some values
1016- res = {}
1017- # Search MSF Private Fund element, because it's valid with all accounts
1018- try:
1019- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1020- 'analytic_account_msf_private_funds')[1]
1021- except ValueError:
1022- fp_id = 0
1023-
1024- # If all elements given, then search FP compatibility
1025- if destination_id and analytic_id and account_id:
1026- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, analytic_id)
1027- # Delete analytic_id if not valid with tuple "account_id/destination_id".
1028- # but do an exception for MSF Private FUND analytic account
1029- 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:
1030- res = {'value': {'analytic_id': False}}
1031- # If no destination, do nothing
1032- elif not destination_id \
1033- or analytic_id == fp_id: # PF always compatible
1034- res = {}
1035- # Otherway: delete FP
1036- else:
1037- res = {'value': {'analytic_id': False}}
1038- return res
1039+ return self.pool.get('analytic.distribution').onchange_ad_destination(cr, uid, ids, destination_id=destination_id,
1040+ funding_pool_id=analytic_id, account_id=account_id,
1041+ fp_field_name='analytic_id')
1042
1043 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, analytic_id=False):
1044- """
1045- Check given cost_center with funding pool
1046- """
1047- # Prepare some values
1048- res = {}
1049- # Search MSF Private Fund element, because it's valid with all accounts
1050- try:
1051- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1052- 'analytic_account_msf_private_funds')[1]
1053- except ValueError:
1054- fp_id = 0
1055+ return self.pool.get('analytic.distribution').\
1056+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=analytic_id, fp_field_name='analytic_id')
1057
1058- if cost_center_id and analytic_id:
1059- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, analytic_id)
1060- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and analytic_id != fp_id:
1061- res = {'value': {'analytic_id': False}}
1062- elif not cost_center_id \
1063- or analytic_id == fp_id: # PF always compatible:
1064- res = {}
1065- else:
1066- res = {'value': {'analytic_id': False}}
1067- return res
1068
1069 analytic_distribution_wizard_fp_lines()
1070
1071+
1072 class analytic_distribution_wizard_f1_lines(osv.osv_memory):
1073 _name = 'analytic.distribution.wizard.f1.lines'
1074 _description = 'analytic.distribution.wizard.lines'
1075
1076=== modified file 'bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py'
1077--- bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py 2015-02-09 12:53:11 +0000
1078+++ bin/addons/analytic_distribution/wizard/commitment_analytic_reallocation.py 2020-11-02 12:53:07 +0000
1079@@ -38,7 +38,7 @@
1080
1081 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1082 """
1083- Change funding pool domain in order to include MSF Private fund
1084+ Adapts domain for AD fields
1085 """
1086 if context is None:
1087 context = {}
1088@@ -55,40 +55,19 @@
1089 for field in fields:
1090 field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
1091 # Change FP field
1092- try:
1093- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1094- except ValueError:
1095- fp_id = 0
1096 fp_fields = form.xpath('//field[@name="funding_pool_id"]')
1097 # Do not use line with account_id, because of NO ACCOUNT_ID PRESENCE!
1098 for field in fp_fields:
1099- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
1100+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
1101+ "('fp_compatible_with_cc_ids', '=', cost_center_id)]")
1102 # NO NEED TO CHANGE DESTINATION_ID FIELD because NO ACCOUNT_ID PRESENCE!
1103 # Apply changes
1104 view['arch'] = etree.tostring(form)
1105 return view
1106
1107 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
1108- """
1109- Check given cost_center with funding pool
1110- """
1111- # Prepare some values
1112- res = {}
1113- if cost_center_id and funding_pool_id:
1114- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
1115- # Search MSF Private Fund element, because it's valid with all accounts
1116- try:
1117- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
1118- 'analytic_account_msf_private_funds')[1]
1119- except ValueError:
1120- fp_id = 0
1121- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and funding_pool_id != fp_id:
1122- res = {'value': {'funding_pool_id': False}}
1123- elif not cost_center_id:
1124- res = {}
1125- else:
1126- res = {'value': {'funding_pool_id': False}}
1127- return res
1128+ return self.pool.get('analytic.distribution').\
1129+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
1130
1131 def button_validate(self, cr, uid ,ids, context=None):
1132 """
1133
1134=== modified file 'bin/addons/analytic_override/analytic_account.py'
1135--- bin/addons/analytic_override/analytic_account.py 2020-10-09 14:34:01 +0000
1136+++ bin/addons/analytic_override/analytic_account.py 2020-11-02 12:53:07 +0000
1137@@ -290,6 +290,107 @@
1138 dom.append(('id', 'in', compatible_dest_ids))
1139 return dom
1140
1141+ def _search_fp_compatible_with_cc_ids(self, cr, uid, obj, name, args, context=None):
1142+ """
1143+ Returns a domain with all funding pools compatible with the selected Cost Center
1144+ E.g.: to get the FPs compatible with the CC 2, use the dom [('fp_compatible_with_cc_ids', '=', 2)]
1145+ """
1146+ dom = []
1147+ if context is None:
1148+ context = {}
1149+ ir_model_data_obj = self.pool.get('ir.model.data')
1150+ for arg in args:
1151+ if arg[0] == 'fp_compatible_with_cc_ids':
1152+ operator = arg[1]
1153+ cc_id = arg[2]
1154+ if operator != '=':
1155+ raise osv.except_osv(_('Error'), _('Filter not implemented on Funding Pools.'))
1156+ cc = False
1157+ if cc_id and isinstance(cc_id, (int, long)):
1158+ cc = self.browse(cr, uid, cc_id, fields_to_fetch=['category', 'type', 'cc_instance_ids'], context=context)
1159+ if cc.category != 'OC' or cc.type == 'view':
1160+ raise osv.except_osv(_('Error'), _('Filter only compatible with a normal-type Cost Center.'))
1161+ compatible_fp_ids = []
1162+ # The Funding Pool PF is compatible with every CC
1163+ try:
1164+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1165+ except ValueError:
1166+ pf_id = 0
1167+ compatible_fp_ids.append(pf_id)
1168+ if cc:
1169+ other_fp_ids = self.search(cr, uid, [('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('id', '!=', pf_id)],
1170+ context=context)
1171+ for fp in self.browse(cr, uid, other_fp_ids,
1172+ fields_to_fetch=['allow_all_cc_with_fp', 'instance_id', 'cost_center_ids'],
1173+ context=context):
1174+ if fp.allow_all_cc_with_fp and fp.instance_id and fp.instance_id.id in [inst.id for inst in cc.cc_instance_ids]:
1175+ compatible = True
1176+ elif cc.id in [c.id for c in fp.cost_center_ids]:
1177+ compatible = True
1178+ else:
1179+ compatible = False
1180+ if compatible:
1181+ compatible_fp_ids.append(fp.id)
1182+ dom.append(('id', 'in', compatible_fp_ids))
1183+ return dom
1184+
1185+ def _search_fp_compatible_with_acc_dest_ids(self, cr, uid, obj, name, args, context=None):
1186+ """
1187+ Returns a domain with all funding pools compatible with the selected Account/Destination combination.
1188+ It requires a tuple with (account, destination), e.g.: to get the FPs compatible with the G/L account 20 and the
1189+ destination 30, use the dom [('fp_compatible_with_acc_dest_ids', '=', (20, 30))]
1190+ """
1191+ fp_ids = []
1192+ if context is None:
1193+ context = {}
1194+ ir_model_data_obj = self.pool.get('ir.model.data')
1195+ account_obj = self.pool.get('account.account')
1196+ for arg in args:
1197+ if arg[0] == 'fp_compatible_with_acc_dest_ids':
1198+ operator = arg[1]
1199+ if operator != '=':
1200+ raise osv.except_osv(_('Error'), _('Filter not implemented on Funding Pools.'))
1201+ acc_dest = arg[2]
1202+ acc_id = dest_id = False
1203+ if acc_dest and isinstance(acc_dest, tuple) and len(acc_dest) == 2:
1204+ acc_id = acc_dest[0]
1205+ dest_id = acc_dest[1]
1206+ # The Funding Pool PF is compatible with everything and must always be displayed
1207+ try:
1208+ pf_id = ir_model_data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
1209+ except ValueError:
1210+ pf_id = 0
1211+ fp_ids.append(pf_id)
1212+ if acc_id and dest_id:
1213+ account_selected = account_obj.browse(cr, uid, acc_id, fields_to_fetch=['destination_ids'], context=context)
1214+ # search for compatible FPs only if the account and destination selected are compatible with one another
1215+ if dest_id in [d.id for d in account_selected.destination_ids]:
1216+ # note: when the link is made to G/L accounts only, all Destinations compatible with the acc. are allowed
1217+ cr.execute('''
1218+ SELECT fp.id
1219+ FROM
1220+ account_analytic_account fp
1221+ LEFT JOIN fp_account_rel ON fp_account_rel.fp_id = fp.id
1222+ LEFT JOIN funding_pool_associated_destinations ON funding_pool_associated_destinations.funding_pool_id = fp.id
1223+ LEFT JOIN account_destination_link link ON link.id = funding_pool_associated_destinations.tuple_id
1224+ WHERE
1225+ fp.category = 'FUNDING' AND
1226+ fp.type != 'view' AND
1227+ fp.id != %(pf_id)s AND
1228+ (
1229+ fp.select_accounts_only = 't' AND
1230+ fp_account_rel.account_id = %(acc_id)s
1231+ OR
1232+ fp.select_accounts_only = 'f' AND
1233+ link.account_id = %(acc_id)s AND
1234+ link.destination_id = %(dest_id)s AND
1235+ link.disabled = 'f'
1236+ )
1237+ ''', {'pf_id': pf_id, 'acc_id': acc_id, 'dest_id': dest_id})
1238+ other_fp_ids = [x[0] for x in cr.fetchall()]
1239+ fp_ids.extend(other_fp_ids)
1240+ return [('id', 'in', fp_ids)]
1241+
1242 def _get_cc_instance_ids(self, cr, uid, ids, fields, arg, context=None):
1243 """
1244 Computes the values for fields.function fields, retrieving:
1245@@ -299,7 +400,8 @@
1246 ...as Cost centre picked for PO/FO reference => po_fo_cc_instance_ids
1247 (Note that those fields should theoretically always be linked to one single instance,
1248 but they are set as one2many in order to be consistent with the type of fields used in the related object.)
1249- - the Missions where the Cost Center is added to => cc_missions
1250+ - the Instances where the Cost Center is added to => cc_instance_ids
1251+ - the related Missions => cc_missions
1252 """
1253 if context is None:
1254 context = {}
1255@@ -311,6 +413,7 @@
1256 top_instance_ids = []
1257 target_instance_ids = []
1258 po_fo_instance_ids = []
1259+ all_instance_ids = []
1260 missions = set()
1261 missions_str = ""
1262 target_cc_ids = acc_target_cc_obj.search(cr, uid, [('cost_center_id', '=', analytic_acc_id)], context=context)
1263@@ -318,6 +421,7 @@
1264 field_list = ['instance_id', 'is_target', 'is_po_fo_cost_center', 'is_top_cost_center']
1265 for target_cc in acc_target_cc_obj.browse(cr, uid, target_cc_ids, fields_to_fetch=field_list, context=context):
1266 instance = target_cc.instance_id
1267+ all_instance_ids.append(instance.id)
1268 if instance.mission:
1269 missions.add(instance.mission)
1270 if target_cc.is_top_cost_center:
1271@@ -333,6 +437,7 @@
1272 'is_target_cc_instance_ids': target_instance_ids,
1273 'po_fo_cc_instance_ids': po_fo_instance_ids,
1274 'cc_missions': missions_str,
1275+ 'cc_instance_ids': all_instance_ids,
1276 }
1277 return res
1278
1279@@ -357,13 +462,22 @@
1280 'dest_cc_ids': fields.many2many('account.analytic.account', 'destination_cost_center_rel',
1281 'destination_id', 'cost_center_id', string='Cost Centers',
1282 domain="[('type', '!=', 'view'), ('category', '=', 'OC')]"),
1283- 'allow_all_cc': fields.boolean(string="Allow all Cost Centers"),
1284+ 'allow_all_cc': fields.boolean(string="Allow all Cost Centers"), # for the Destinations
1285+ 'allow_all_cc_with_fp': fields.boolean(string="Allow all Cost Centers"), # for the Funding Pools
1286 'dest_compatible_with_cc_ids': fields.function(_get_fake, method=True, store=False,
1287 string='Destinations compatible with the Cost Center',
1288 type='many2many', relation='account.analytic.account',
1289 fnct_search=_search_dest_compatible_with_cc_ids),
1290 'dest_without_cc': fields.function(_get_dest_without_cc, type='boolean', method=True, store=False,
1291 string="Destination allowing no Cost Center",),
1292+ 'fp_compatible_with_cc_ids': fields.function(_get_fake, method=True, store=False,
1293+ string='Funding Pools compatible with the Cost Center',
1294+ type='many2many', relation='account.analytic.account',
1295+ fnct_search=_search_fp_compatible_with_cc_ids),
1296+ 'fp_compatible_with_acc_dest_ids': fields.function(_get_fake, method=True, store=False,
1297+ string='Funding Pools compatible with the Account/Destination combination',
1298+ type='many2many', relation='account.analytic.account',
1299+ fnct_search=_search_fp_compatible_with_acc_dest_ids),
1300 'top_cc_instance_ids': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1301 string="Instances having the CC as Top CC",
1302 type="one2many", relation="msf.instance", multi="cc_instances"),
1303@@ -376,12 +490,21 @@
1304 'cc_missions': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1305 string="Missions where the CC is added to",
1306 type='char', multi="cc_instances"),
1307+ 'cc_instance_ids': fields.function(_get_cc_instance_ids, method=True, store=False, readonly=True,
1308+ string="Instances where the CC is added to",
1309+ type="one2many", relation="msf.instance", multi="cc_instances"),
1310+ 'select_accounts_only': fields.boolean(string="Select Accounts Only"),
1311+ 'fp_account_ids': fields.many2many('account.account', 'fp_account_rel', 'fp_id', 'account_id', string='G/L Accounts',
1312+ domain="[('type', '!=', 'view'), ('is_analytic_addicted', '=', True), ('active', '=', 't')]",
1313+ help="G/L accounts linked to the Funding Pool", order_by='code'),
1314 }
1315
1316 _defaults ={
1317 'date_start': lambda *a: (datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d'),
1318 'for_fx_gain_loss': lambda *a: False,
1319 'allow_all_cc': lambda *a: False,
1320+ 'allow_all_cc_with_fp': lambda *a: False,
1321+ 'select_accounts_only': lambda *a: False,
1322 }
1323
1324 def _check_code_unicity(self, cr, uid, ids, context=None):
1325@@ -453,30 +576,40 @@
1326 res['domain']['parent_id'] = [('category', '=', category), ('type', '=', 'view')]
1327 return res
1328
1329- def on_change_allow_all_cc(self, cr, uid, ids, allow_all_cc, dest_cc_ids, context=None):
1330+ 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):
1331 """
1332 If the user tries to tick the box "Allow all Cost Centers" whereas CC are selected,
1333 informs him that he has to remove the CC first
1334+ (acc_type = name of the Analytic Account Type to which the CC are linked, displayed in the warning msg)
1335 """
1336 res = {}
1337- if allow_all_cc and dest_cc_ids and dest_cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1338+ if allow_all_cc and cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1339+ # NOTE: the msg is stored in a variable on purpose, otherwise the ".po" translation files would wrongly contain Python code
1340+ msg = 'Please remove the Cost Centers linked to the %s before ticking this box.' % acc_type.title()
1341 warning = {
1342 'title': _('Warning!'),
1343- 'message': _('Please remove the Cost Centers linked to the Destination before ticking this box.')
1344+ 'message': _(msg)
1345 }
1346 res['warning'] = warning
1347- res['value'] = {'allow_all_cc': False, }
1348+ res['value'] = {field_name: False, }
1349 return res
1350
1351- def on_change_dest_cc_ids(self, cr, uid, ids, dest_cc_ids, context=None):
1352+ def on_change_allow_all_cc_with_fp(self, cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, context=None):
1353+ return self.on_change_allow_all_cc(cr, uid, ids, allow_all_cc_with_fp, cost_center_ids, acc_type='funding pool',
1354+ field_name='allow_all_cc_with_fp', context=context)
1355+
1356+ def on_change_cc_ids(self, cr, uid, ids, cc_ids, field_name='allow_all_cc', context=None):
1357 """
1358 If at least a CC is selected, unticks the box "Allow all Cost Centers"
1359 """
1360 res = {}
1361- if dest_cc_ids and dest_cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1362- res['value'] = {'allow_all_cc': False, }
1363+ if cc_ids and cc_ids[0][2]: # e.g. [(6, 0, [1, 2])]
1364+ res['value'] = {field_name: False, }
1365 return res
1366
1367+ def on_change_cc_with_fp(self, cr, uid, ids, cost_center_ids, context=None):
1368+ return self.on_change_cc_ids(cr, uid, ids, cost_center_ids, field_name='allow_all_cc_with_fp', context=context)
1369+
1370 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1371 if not context:
1372 context = {}
1373@@ -539,12 +672,12 @@
1374 vals['parent_id'] = funding_pool_parent
1375
1376 def remove_inappropriate_links(self, vals, context=None):
1377- '''
1378- Remove relations that are incoherent regarding the category selected. For instance an account with
1379- category "Funding Pool" can have associated cost centers, whereas a "Destination" shouldn't.
1380+ """
1381+ Removes relations that are inconsistent regarding the category selected. For instance an account with the
1382+ category "Funding Pool" can have associated cost centers, whereas a "Cost Center" shouldn't.
1383 (That would happen if the category is modified after that the relations have been created).
1384 :return: corrected vals
1385- '''
1386+ """
1387 if context is None:
1388 context = {}
1389 if 'category' in vals:
1390@@ -555,6 +688,15 @@
1391 if vals['category'] != 'FUNDING':
1392 vals['tuple_destination_account_ids'] = [(6, 0, [])]
1393 vals['cost_center_ids'] = [(6, 0, [])]
1394+ vals['allow_all_cc_with_fp'] = False # default value
1395+ vals['select_accounts_only'] = False
1396+ vals['fp_account_ids'] = [(6, 0, [])]
1397+ # Funding Pools: either "Account/Destination combinations" or "G/L accounts only" must be stored
1398+ if vals['category'] == 'FUNDING' and 'select_accounts_only' in vals:
1399+ if vals['select_accounts_only']:
1400+ vals['tuple_destination_account_ids'] = [(6, 0, [])]
1401+ else:
1402+ vals['fp_account_ids'] = [(6, 0, [])]
1403 return vals
1404
1405 def _check_date(self, vals):
1406@@ -720,6 +862,56 @@
1407 'target': 'current',
1408 }
1409
1410+ def get_cc_linked_to_fp(self, cr, uid, fp_id, context=None):
1411+ """
1412+ Returns a browse record list of all Cost Centers compatible with the Funding Pool in parameter:
1413+ - if "Allow all Cost Centers" is ticked: all CC linked to the prop. instance of the FP
1414+ - else all CC selected in the FP form.
1415+
1416+ Note: this method matches with what has been selected in the Cost centers tab of the FP form.
1417+ It returns an empty list for PF.
1418+ """
1419+ if context is None:
1420+ context = {}
1421+ cc_list = []
1422+ fp = self.browse(cr, uid, fp_id,
1423+ fields_to_fetch=['category', 'allow_all_cc_with_fp', 'instance_id', 'cost_center_ids'],
1424+ context=context)
1425+ if fp.category == 'FUNDING':
1426+ if fp.allow_all_cc_with_fp and fp.instance_id:
1427+ # inactive CC are included on purpose, to match with selectable CC in FP form
1428+ for cc_id in self.search(cr, uid, [('category', '=', 'OC'), ('type', '!=', 'view')], order='code', context=context):
1429+ cc = self.browse(cr, uid, cc_id, context=context)
1430+ if fp.instance_id.id in [inst.id for inst in cc.cc_instance_ids]:
1431+ cc_list.append(cc)
1432+ else:
1433+ cc_list = fp.cost_center_ids or []
1434+ return cc_list
1435+
1436+ def get_acc_dest_linked_to_fp(self, cr, uid, fp_id, context=None):
1437+ """
1438+ Returns a tuple of all combinations of (account_id, destination_id) compatible with the FP in parameter:
1439+ - if "Select Accounts Only" is ticked: the accounts selected and the Destinations compatible with them
1440+ - else the Account/Destination combinations selected.
1441+
1442+ Note: this method matches with what has been selected in the Accounts/Destinations tab of the FP form.
1443+ It returns an empty list for PF.
1444+ """
1445+ if context is None:
1446+ context = {}
1447+ combinations = []
1448+ fp = self.browse(cr, uid, fp_id,
1449+ fields_to_fetch=['category', 'select_accounts_only', 'fp_account_ids', 'tuple_destination_account_ids'],
1450+ context=context)
1451+ if fp.category == 'FUNDING':
1452+ if fp.select_accounts_only:
1453+ for account in fp.fp_account_ids:
1454+ for dest in account.destination_ids:
1455+ combinations.append((account.id, dest.id))
1456+ else:
1457+ combinations = [(t.account_id.id, t.destination_id.id) for t in fp.tuple_destination_account_ids if not t.disabled]
1458+ return combinations
1459+
1460 def button_cc_clear(self, cr, uid, ids, context=None):
1461 self.write(cr, uid, ids, {'cost_center_ids':[(6, 0, [])]}, context=context)
1462 return True
1463@@ -735,6 +927,13 @@
1464 self.write(cr, uid, ids, {'tuple_destination_account_ids':[(6, 0, [])]}, context=context)
1465 return True
1466
1467+ def button_fp_account_clear(self, cr, uid, ids, context=None):
1468+ """
1469+ Removes all G/L accounts selected in the Funding Pool view
1470+ """
1471+ self.write(cr, uid, ids, {'fp_account_ids': [(6, 0, [])]}, context=context)
1472+ return True
1473+
1474 def get_destinations_by_accounts(self, cr, uid, ids, context=None):
1475 """
1476 Returns a view with the Destinations by accounts (for the FP selected if any, otherwise for all the FP)
1477
1478=== modified file 'bin/addons/financing_contract/contract.py'
1479--- bin/addons/financing_contract/contract.py 2019-10-10 16:20:57 +0000
1480+++ bin/addons/financing_contract/contract.py 2020-11-02 12:53:07 +0000
1481@@ -62,6 +62,10 @@
1482 return True
1483
1484 def create(self, cr, uid, vals, context=None):
1485+ if context is None:
1486+ context = {}
1487+ analytic_acc_obj = self.pool.get('account.analytic.account')
1488+ format_obj = self.pool.get('financing.contract.format')
1489 # US-113: Check if the call is from sync update
1490 if context.get('sync_update_execution') and vals.get('contract_id', False):
1491 # US-113: and if there is any financing contract existed for this format, if no, then ignore this call
1492@@ -75,26 +79,18 @@
1493
1494 #US-345: the following block cannot be executed in the sync context, because it would then reset all costcenters from the funding pools!
1495 # making that the deleted costcenters from the sender were not taken into account
1496- if not context.get('sync_update_execution') and 'contract_id' in vals and 'funding_pool_id' in vals:
1497- # get the cc ids from for this funding pool
1498- quad_obj = self.pool.get('financing.contract.account.quadruplet')
1499- quad_ids = quad_obj.search(cr, uid, [('funding_pool_id','=',vals['funding_pool_id'])],context=context)
1500- quad_rows = quad_obj.browse(cr, uid, quad_ids,context=context)
1501- quad_cc_ids = []
1502- for quad in quad_rows:
1503- cc_id_temp = quad.cost_center_id.id
1504- if cc_id_temp not in quad_cc_ids:
1505- quad_cc_ids.append(cc_id_temp)
1506+ if not context.get('sync_update_execution') and vals.get('contract_id') and vals.get('funding_pool_id'):
1507+ # get the Cost Centers linked to the Funding Pool
1508+ fp_cc_ids = [c.id for c in analytic_acc_obj.get_cc_linked_to_fp(cr, uid, vals['funding_pool_id'], context=context)]
1509
1510 # get the format instance
1511- format_obj = self.pool.get('financing.contract.format')
1512 cc_rows = format_obj.browse(cr, uid, vals['contract_id'], context=context).cost_center_ids
1513 cc_ids = []
1514 for cc in cc_rows:
1515 cc_ids.append(cc.id)
1516
1517 # append the ccs from the fp only if not already there
1518- cc_ids = list(set(cc_ids).union(quad_cc_ids))
1519+ cc_ids = list(set(cc_ids).union(fp_cc_ids))
1520 # replace the associated cc list -NOT WORKING
1521 format_obj.write(cr, uid, vals['contract_id'],{'cost_center_ids':[(6,0,cc_ids)]}, context=context)
1522 # UFTP-121: Check that FP is not used yet.
1523@@ -337,6 +333,7 @@
1524 ondelete="cascade", required=True),
1525 'fp_added_flag': fields.boolean('Flag when new FP is added'),
1526 'instance_level': fields.function(_get_instance_level, method=True, string="Current instance level", type="char", readonly=True), # UFTP-343
1527+ 'quad_gen_date': fields.datetime('Date of last generation of quad'),
1528 }
1529
1530 _defaults = {
1531
1532=== modified file 'bin/addons/financing_contract/financing_contract_account_quadruplet.py'
1533--- bin/addons/financing_contract/financing_contract_account_quadruplet.py 2017-10-02 15:51:29 +0000
1534+++ bin/addons/financing_contract/financing_contract_account_quadruplet.py 2020-11-02 12:53:07 +0000
1535@@ -21,18 +21,48 @@
1536
1537 from osv import fields, osv
1538 from tools import sql
1539+import time
1540
1541 class financing_contract_account_quadruplet(osv.osv):
1542 _name = 'financing.contract.account.quadruplet'
1543 _rec_name = 'cost_center_id'
1544 _description = 'FP / CC / destination valid values view'
1545- _auto = False
1546-
1547+ _log_access = False
1548+ _auto = True
1549+
1550+ def migrate_old_quad(self, cr, uid, ids, context=None):
1551+ '''
1552+ ids: list of record in the old view
1553+ return list of new record
1554+ '''
1555+
1556+ new_ids = []
1557+ for old_id in ids:
1558+ # DO UPDATE: to return an id
1559+ cr.execute('''
1560+ INSERT INTO financing_contract_account_quadruplet
1561+ (account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id)
1562+ ( select
1563+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
1564+ from
1565+ financing_contract_account_quadruplet_old
1566+ where
1567+ id=%s
1568+ ) ON CONFLICT ON CONSTRAINT financing_contract_account_quadruplet_check_unique DO UPDATE SET disabled=EXCLUDED.disabled
1569+ RETURNING id ''', (old_id,))
1570+ ret = cr.fetchone()
1571+ if ret:
1572+ new_ids.append(ret[0])
1573+ return new_ids
1574
1575 def _auto_init(self, cr, context=None):
1576+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet')
1577+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet_view')
1578 res = super(financing_contract_account_quadruplet, self)._auto_init(cr, context)
1579- sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet')
1580- cr.execute("""CREATE OR REPLACE VIEW financing_contract_account_quadruplet AS (
1581+ sql.drop_view_if_exists(cr, 'financing_contract_account_quadruplet_old')
1582+
1583+ # old sql view kept to manage migration and old sync updates
1584+ cr.execute('''CREATE OR REPLACE VIEW financing_contract_account_quadruplet_old AS (
1585 SELECT abs(('x'||substr(md5(fp.code || cc.code || lnk.name),1,16))::bit(32)::int) as id,
1586 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
1587 FROM account_analytic_account fp,
1588@@ -44,9 +74,133 @@
1589 AND fpacc.cost_center_id = cc.id
1590 AND lnk.id = fpad.tuple_id
1591 AND fp.id = fpad.funding_pool_id
1592- ORDER BY lnk.name, cc.code DESC)""")
1593+ ORDER BY lnk.name, cc.code DESC)
1594+ ''')
1595+
1596+ cr.execute("""CREATE OR REPLACE VIEW financing_contract_account_quadruplet_view AS (
1597+ SELECT account_destination_id, cost_center_id, funding_pool_id, account_destination_name, account_id, disabled, account_destination_link_id FROM
1598+ (
1599+ -- all cc = f, G/L = f
1600+ SELECT
1601+ 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
1602+ FROM account_analytic_account fp,
1603+ account_analytic_account cc,
1604+ funding_pool_associated_cost_centers fpacc,
1605+ funding_pool_associated_destinations fpad,
1606+ account_destination_link lnk
1607+ WHERE
1608+ fpacc.funding_pool_id = fp.id AND
1609+ fpacc.cost_center_id = cc.id AND
1610+ lnk.id = fpad.tuple_id AND
1611+ fp.id = fpad.funding_pool_id
1612+
1613+ UNION
1614+
1615+ -- all cc = t, G/L = t
1616+ select
1617+ 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
1618+ FROM
1619+ account_analytic_account fp,
1620+ account_analytic_account cc,
1621+ fp_account_rel,
1622+ account_target_costcenter target,
1623+ account_destination_link lnk,
1624+ account_account gl_account
1625+ where
1626+ fp.allow_all_cc_with_fp = 't' and
1627+ cc.type != 'view' and
1628+ cc.category = 'OC' and
1629+ target.cost_center_id = cc.id and
1630+ target.instance_id = fp.instance_id and
1631+ fp.select_accounts_only = 't' and
1632+ fp_account_rel.fp_id = fp.id and
1633+ fp_account_rel.account_id= gl_account.id and
1634+ lnk.account_id = gl_account.id
1635+
1636+ UNION
1637+
1638+ -- all cc = f, G/L = t
1639+ select
1640+ 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
1641+ FROM
1642+ account_analytic_account fp,
1643+ account_analytic_account cc,
1644+ funding_pool_associated_cost_centers fpacc,
1645+ fp_account_rel,
1646+ account_destination_link lnk,
1647+ account_account gl_account
1648+ where
1649+ fp.allow_all_cc_with_fp = 'f' and
1650+ fpacc.funding_pool_id = fp.id and
1651+ fpacc.cost_center_id = cc.id and
1652+ fp.select_accounts_only = 't' and
1653+ fp_account_rel.fp_id = fp.id and
1654+ fp_account_rel.account_id= gl_account.id and
1655+ lnk.account_id = gl_account.id
1656+
1657+ UNION
1658+
1659+ -- all cc = t , G/L = f
1660+ select
1661+ 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
1662+ FROM
1663+ account_analytic_account fp,
1664+ account_analytic_account cc,
1665+ funding_pool_associated_destinations fpad,
1666+ account_target_costcenter target,
1667+ account_destination_link lnk
1668+ where
1669+ fp.allow_all_cc_with_fp = 't' and
1670+ cc.type != 'view' and
1671+ cc.category = 'OC' and
1672+ target.cost_center_id = cc.id and
1673+ target.instance_id = fp.instance_id and
1674+ fp.select_accounts_only = 'f' and
1675+ lnk.id = fpad.tuple_id and
1676+ fp.id = fpad.funding_pool_id
1677+ ) AS combinations
1678+ )""")
1679 return res
1680
1681+ def gen_quadruplet(self, cr, uid, context=None):
1682+ '''
1683+ triggered by unifield-web to generate the list of quadruplets for this contract
1684+ record the last generation date to refresh only if the contract, a dest link or a ana. acccount is modified
1685+ '''
1686+ if context is None:
1687+ context = {}
1688+ contract_id = context.get('contract_id', False)
1689+ if contract_id:
1690+ ctr_obj = self.pool.get('financing.contract.contract')
1691+ contract = ctr_obj.browse(cr, uid, context['contract_id'], fields_to_fetch=['funding_pool_ids', 'cost_center_ids', 'quad_gen_date'], context=context)
1692+ cr.execute('''select max(last_modification) from ir_model_data where module='sd' and (
1693+ model in ('account.analytic.account', 'account.destination.link') or (model = 'financing.contract.contract' and res_id = %s)
1694+ )''', (contract_id,))
1695+ last_obj_modified = cr.fetchone()[0]
1696+ 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'):
1697+ # ignore quad_gen_date in the future
1698+ cc_ids = [cc.id for cc in contract.cost_center_ids]
1699+ fp_ids = [fp.funding_pool_id.id for fp in contract.funding_pool_ids]
1700+ if not cc_ids:
1701+ # do not traceback if cc / fp not set on contract
1702+ cc_ids = [0]
1703+ if not fp_ids:
1704+ fp_ids = [0]
1705+ cr.execute('''
1706+ INSERT INTO financing_contract_account_quadruplet
1707+ (account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id)
1708+ (select
1709+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
1710+ from
1711+ financing_contract_account_quadruplet_view
1712+ where
1713+ funding_pool_id in %s and
1714+ cost_center_id in %s
1715+ )
1716+ ON CONFLICT ON CONSTRAINT financing_contract_account_quadruplet_check_unique DO UPDATE SET disabled=EXCLUDED.disabled''', (tuple(fp_ids), tuple(cc_ids)))
1717+ cr.execute('update financing_contract_contract set quad_gen_date=%s where id=%s', (last_obj_modified, contract_id))
1718+
1719+ return True
1720
1721 # The result set with {ID:Flag} if Flag=True, the line will be grey, otherwise, it is selectable
1722 def _get_used_in_contract(self, cr, uid, ids, field_name, arg, context=None):
1723@@ -67,8 +221,8 @@
1724 # TODO this should be renamed format_id
1725 cr.execute('''select account_quadruplet_id
1726 from financing_contract_actual_account_quadruplets
1727- where actual_line_id in (select id from financing_contract_format_line
1728- where format_id = %s and is_quadruplet is true)''', (contract.format_id.id,))
1729+ where account_quadruplet_id in %s and actual_line_id in (select id from financing_contract_format_line
1730+ where format_id = %s and is_quadruplet is true)''', (tuple(ids), contract.format_id.id,))
1731 rows = cr.fetchall()
1732 for id in [x[0] for x in rows]:
1733 exclude[id] = True
1734@@ -78,9 +232,13 @@
1735 if not active_id or line.id != active_id:
1736 for account_destination in line.account_destination_ids:
1737 # search the quadruplet to exclude
1738- quadruplet_ids_to_exclude = self.search(cr, uid, [('account_id', '=', account_destination.account_id.id),('account_destination_id','=',account_destination.destination_id.id)])
1739+ 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)])
1740 for item in quadruplet_ids_to_exclude:
1741 exclude[item] = True
1742+ for account in line.reporting_account_ids:
1743+ # exclude the quadruplets when the account has been selected in lines with "accounts only"
1744+ for quad in self.search(cr, uid, [('account_id', '=', account.id)], order='NO_ORDER', context=context):
1745+ exclude[quad] = True
1746
1747 for id in ids:
1748 ids_to_exclude[id] = id in exclude
1749@@ -90,35 +248,20 @@
1750 res = {}
1751 if context is None:
1752 context = {}
1753- exclude = {}
1754+ res = {}
1755
1756 if not context.get('contract_id'):
1757 for id in ids:
1758 res[id] = False
1759 return res
1760
1761- ctr_obj = self.pool.get('financing.contract.contract')
1762- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1763- # financing_contract_funding_pool_line.contract_id is a FK for financing_contract_format.id
1764- # TODO this should be renamed format_id during refactoring
1765- exclude = {}
1766- cr.execute('''select id from financing_contract_account_quadruplet
1767- where funding_pool_id in
1768- (select funding_pool_id
1769- from financing_contract_funding_pool_line
1770- where contract_id = %s)
1771- and exists (select 'X'
1772- from financing_contract_cost_center cc
1773- where cc.contract_id = %s
1774- and cc.cost_center_id =
1775- financing_contract_account_quadruplet.cost_center_id)''', (contract.format_id.id,contract.format_id.id))
1776- for id in [x[0] for x in cr.fetchall()]:
1777- exclude[id] = True
1778- for id in ids:
1779- res[id] = id in exclude
1780+ for _id in ids:
1781+ res[_id] = False
1782+
1783+ for _id in self.search(cr, uid, [('can_be_used', '=', True)], context=context):
1784+ res[_id] = True
1785 return res
1786
1787-
1788 def _search_can_be(self, cr, uid, ids, field_name, arg, context=None):
1789 res = {}
1790 if context is None:
1791@@ -130,67 +273,30 @@
1792 return res
1793
1794 ctr_obj = self.pool.get('financing.contract.contract')
1795- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1796- cr.execute('''select id from financing_contract_account_quadruplet
1797- where funding_pool_id in
1798- (select funding_pool_id
1799- from financing_contract_funding_pool_line
1800- where contract_id = %s)
1801- and exists (select 'X'
1802- from financing_contract_cost_center cc
1803- where cc.contract_id = %s
1804- and cc.cost_center_id =
1805- financing_contract_account_quadruplet.cost_center_id)''', (contract.format_id.id,contract.format_id.id))
1806- someids = []
1807- someids += [x[0] for x in cr.fetchall()]
1808- return [('id','in',someids)]
1809-
1810-
1811-
1812- def _search_used_in_contract(self, cr, uid, obj, name, args, context=None):
1813- if not args:
1814- return []
1815- if context is None:
1816- context = {}
1817- assert args[0][1] == '=' and args[0][2], 'Filter not implemented'
1818- if not context.get('contract_id'):
1819- return []
1820-
1821- ctr_obj = self.pool.get('financing.contract.contract')
1822- contract = ctr_obj.browse(cr, uid, context['contract_id'])
1823-
1824- exclude = []
1825- for line in contract.actual_line_ids:
1826- if context.get('active_id', False) and line.id != context['active_id']:
1827- for account_destination in line.account_destination_ids:
1828- cr.execute('''select account_quadruplet_id
1829- from financing_contract_actual_account_quadruplets
1830- where actual_line_id in (select l.id from financing_contract_contract c,
1831- financing_contract_format f,
1832- financing_contract_format_line l
1833- where c.id = %s
1834- and f.id = c.format_id
1835- and l.format_id = f.id)''', (contract.format_id.id,))
1836- exclude += [x[0] for x in cr.fetchall()]
1837- for account_quadruplet in line.account_quadruplet_ids:
1838- exclude.append(account_quadruplet.id)
1839- return [('id', 'not in', exclude)]
1840-
1841- #columns for view
1842+ contract = ctr_obj.browse(cr, uid, context['contract_id'], fields_to_fetch=['funding_pool_ids', 'cost_center_ids'])
1843+ cc_ids = [cc.id for cc in contract.cost_center_ids]
1844+ fp_ids = [fp.funding_pool_id.id for fp in contract.funding_pool_ids]
1845+ return [('cost_center_id', 'in', cc_ids), ('funding_pool_id', 'in', fp_ids)]
1846+
1847 _columns = {
1848- 'account_destination_id': fields.many2one('account.destination.link', 'Account/Destination', relate=True, readonly=True),
1849- 'cost_center_id': fields.many2one('account.analytic.account', 'Cost Centre', relate=True, readonly=True),
1850- 'funding_pool_id': fields.many2one('account.analytic.account', 'Funding Pool', relate=True, readonly=True),
1851- 'account_destination_name': fields.char('Account', size=64, readonly=True),
1852- 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used', fnct_search=_search_used_in_contract),
1853+ 'account_destination_id': fields.many2one('account.analytic.account', 'Destination', relate=True, readonly=True, select=1),
1854+ 'cost_center_id': fields.many2one('account.analytic.account', 'Cost Centre', relate=True, readonly=True, select=1),
1855+ 'funding_pool_id': fields.many2one('account.analytic.account', 'Funding Pool', relate=True, readonly=True, select=1),
1856+ 'account_destination_name': fields.char('Account', size=64, readonly=True, select=1),
1857+ 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used'),
1858 'can_be_used': fields.function(_can_be_used_in_contract, method=True, type='boolean', string='Can', fnct_search=_search_can_be),
1859- 'account_id': fields.many2one('account.destination.link', 'Account ID', relate=True, readonly=True),
1860- 'account_destination_link_id': fields.many2one('account.destination.link', 'Link id', readonly=True),
1861+ 'account_id': fields.many2one('account.account', 'Account ID', relate=True, readonly=True, select=1),
1862+ 'account_destination_link_id': fields.many2one('account.destination.link', 'Link id', readonly=True, select=1),
1863 'disabled': fields.boolean('Disabled'),
1864 }
1865
1866- _order = 'account_destination_name asc, funding_pool_id asc, cost_center_id asc'
1867+ _sql_constraints = {
1868+ ('check_unique',
1869+ 'unique (account_destination_id, cost_center_id, funding_pool_id, account_id, account_destination_link_id)',
1870+ 'not unique!')
1871+ }
1872+ _order = 'account_destination_name asc, funding_pool_id asc, cost_center_id asc, id'
1873+
1874
1875 financing_contract_account_quadruplet()
1876 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1877-
1878
1879=== modified file 'bin/addons/financing_contract/financing_contract_view.xml'
1880--- bin/addons/financing_contract/financing_contract_view.xml 2020-01-30 10:18:45 +0000
1881+++ bin/addons/financing_contract/financing_contract_view.xml 2020-11-02 12:53:07 +0000
1882@@ -105,20 +105,54 @@
1883 <field name="overhead_type" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1884 <field name="overhead_percentage" colspan="2" attrs="{'required': [('line_type', '=', 'overhead')]}"/>
1885 </group>
1886- <button name="button_delete_all_quads" type="object" string="Remove all quads" icon="gtk-clear" colspan="1"
1887- attrs="{'invisible': [('is_quadruplet', '=', False)]}" />
1888- <button name="button_delete_all_couples" type="object" string="Remove all couples" icon="gtk-clear" colspan="1"
1889- attrs="{'invisible': [('is_quadruplet', '=', True)]}" />
1890- <field name="is_quadruplet" attrs="{'invisible': [('line_type', '=', 'view')]}"/>
1891+ <group colspan="6" col="20">
1892+ <button name="button_delete_all_quads" type="object" string="Remove all quads"
1893+ icon="gtk-clear" colspan="4"
1894+ attrs="{'invisible': ['|',
1895+ ('is_quadruplet', '=', False),
1896+ ('line_type', '=', 'view')]}" />
1897+ <button name="button_delete_all_couples" type="object" string="Remove all couples"
1898+ icon="gtk-clear" colspan="4"
1899+ attrs="{'invisible': ['|', '|',
1900+ ('is_quadruplet', '=', True),
1901+ ('reporting_select_accounts_only', '=', True),
1902+ ('line_type', '=', 'view')]}"
1903+ />
1904+ <button name="button_remove_all_accounts" type="object" string="Remove all accounts"
1905+ icon="gtk-clear" colspan="4"
1906+ attrs="{'invisible': ['|',
1907+ ('reporting_select_accounts_only', '=', False),
1908+ ('line_type', '=', 'view')]}" />
1909+ <label string="" colspan="1"/>
1910+ <field name="is_quadruplet" attrs="{'invisible': [('line_type', '=', 'view')]}"
1911+ colspan="2"
1912+ on_change="on_change_is_quadruplet(is_quadruplet)"
1913+ />
1914+ <field name="reporting_select_accounts_only" attrs="{'invisible': [('line_type', '=', 'view')]}"
1915+ colspan="2"
1916+ on_change="on_change_reporting_select_accounts_only(reporting_select_accounts_only)"
1917+ />
1918+ <label string="" colspan="3"/>
1919+ </group>
1920 <field name="account_destination_ids" colspan="4" string="Account/Destination" nolabel="1"
1921 context="{'search_default_active': 1}"
1922- attrs="{'invisible': [('|'), ('line_type', '=', 'view'), ('is_quadruplet', '=', True)]}">
1923- </field>
1924+ attrs="{'invisible': ['|', '|',
1925+ ('line_type', '=', 'view'),
1926+ ('is_quadruplet', '=', True),
1927+ ('reporting_select_accounts_only', '=', True)]}"
1928+ />
1929 <field name="account_quadruplet_ids" colspan="4" string="Account/Destination/Funding Pool/Cost Centre" nolabel="1"
1930 context="{'search_default_active': 1}"
1931 attrs="{'invisible': [('|'), ('line_type', '=', 'view'), ('is_quadruplet', '!=', True)]}"
1932 domain="[('can_be_used','=',True)]">
1933 </field>
1934+ <field name="reporting_account_ids" colspan="4" string="G/L Accounts" nolabel="1"
1935+ context="{'from_contract': True, 'search_default_active': 1}"
1936+ attrs="{'invisible': ['|',
1937+ ('line_type', '=', 'view'),
1938+ ('reporting_select_accounts_only', '=', False)]}"
1939+ domain="[('selectable_in_contract', '=', True)]"
1940+ />
1941 </form>
1942 </field>
1943 </page>
1944@@ -175,7 +209,6 @@
1945 <field name="type">tree</field>
1946 <field name="arch" type="xml">
1947 <tree string="Account/Destination/Funding Pool/Cost Centre" colors="grey:used_in_contract;red:disabled" notselectable="used_in_contract" editable="top" noteditable="1">
1948- <field name="can_be_used" invisible="1"/>
1949 <field name="account_destination_name"/>
1950 <field name="funding_pool_id"/>
1951 <field name="cost_center_id"/>
1952
1953=== modified file 'bin/addons/financing_contract/format.py'
1954--- bin/addons/financing_contract/format.py 2015-05-28 08:50:00 +0000
1955+++ bin/addons/financing_contract/format.py 2020-11-02 12:53:07 +0000
1956@@ -97,39 +97,17 @@
1957 duplet_ids_to_exclude = self.search(cr, uid, [('account_id', '=', account_quadruplet.account_id.id),('destination_id','=',account_quadruplet.account_destination_id.id)])
1958 for item in duplet_ids_to_exclude:
1959 exclude[item] = True
1960+ for account in line.reporting_account_ids:
1961+ # exclude the acc/dest combinations when the account has been selected in lines with "accounts only"
1962+ for acc_dest in self.search(cr, uid, [('account_id', '=', account.id)], order='NO_ORDER', context=context):
1963+ exclude[acc_dest] = True
1964
1965 for id in ids:
1966 ids_to_exclude[id] = id in exclude
1967 return ids_to_exclude
1968
1969- def _search_used_in_contract(self, cr, uid, obj, name, args, context=None):
1970- if not args:
1971- return []
1972- if context is None:
1973- context = {}
1974- assert args[0][1] == '=' and args[0][2], 'Filter not implemented'
1975- if not context.get('contract_id') and not context.get('donor_id'):
1976- return []
1977-
1978- if context.get('contract_id'):
1979- ctr_obj = self.pool.get('financing.contract.contract')
1980- id_toread = context['contract_id']
1981- elif context.get('donor_id'):
1982- ctr_obj = self.pool.get('financing.contract.donor')
1983- id_toread = context['donor_id']
1984-
1985- exclude = {}
1986- for line in ctr_obj.browse(cr, uid, id_toread).actual_line_ids:
1987- if not context.get('active_id', False) or line.id != context['active_id']:
1988- for account_destination in line.account_destination_ids:
1989- exclude[account_destination.id] = True
1990- for account_quadruplet in line.account_quadruplet_ids:
1991- exclude[account_quadruplet.account_destination_id.id] = True
1992-
1993- return [('id', 'not in', exclude.keys())]
1994-
1995 _columns = {
1996- 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used', fnct_search=_search_used_in_contract),
1997+ 'used_in_contract': fields.function(_get_used_in_contract, method=True, type='boolean', string='Used'),
1998 }
1999
2000 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2001
2002=== modified file 'bin/addons/financing_contract/format_line.py'
2003--- bin/addons/financing_contract/format_line.py 2019-10-30 16:33:21 +0000
2004+++ bin/addons/financing_contract/format_line.py 2020-11-02 12:53:07 +0000
2005@@ -22,7 +22,7 @@
2006 from osv import fields, osv
2007 from analytic_distribution.destination_tools import many2many_sorted
2008 from account_override import ACCOUNT_RESTRICTED_AREA
2009-
2010+from tools.safe_eval import safe_eval
2011
2012 class financing_contract_format_line(osv.osv):
2013
2014@@ -63,50 +63,47 @@
2015 return domain
2016
2017 # get list of accounts for duplet format lines
2018- def _create_account_couple_domain(self, account_destination_list, general_domain):
2019- if len(account_destination_list) == 0:
2020- return False # Just make this condition to False
2021- elif len(account_destination_list) == 1:
2022- temp_domain = ['&',
2023- ('general_account_id', '=', account_destination_list[0].account_id.id),
2024- ('destination_id', '=', account_destination_list[0].destination_id.id)]
2025-
2026- return temp_domain
2027- else:
2028- firstElement = self._create_account_couple_domain([account_destination_list[0]], general_domain)
2029- secondElement = self._create_account_couple_domain(account_destination_list[1:], general_domain)
2030-
2031- if firstElement and secondElement:
2032- return ['|'] + firstElement + secondElement
2033- elif firstElement:
2034- return firstElement
2035- return secondElement
2036+ def _create_account_couple_domain(self, account_destination_list):
2037+ """
2038+ Returns the domain corresponding to the list of acc/dest in param.
2039+ """
2040+ dom = []
2041+ if not account_destination_list:
2042+ return dom
2043+ first = True
2044+ for account_dest in account_destination_list:
2045+ dom += ['&', ('general_account_id', '=', account_dest.account_id.id), ('destination_id', '=', account_dest.destination_id.id)]
2046+ if not first:
2047+ dom.insert(0, '|')
2048+ else:
2049+ first = False
2050+ return dom
2051
2052 # get list of accounts for quadruplet format lines
2053- def _create_account_quadruplet_domain(self, account_quadruplet_list, funding_pool_ids=False):
2054- if len(account_quadruplet_list) == 0:
2055- return False
2056- elif len(account_quadruplet_list) == 1:
2057- if account_quadruplet_list[0].funding_pool_id.id in funding_pool_ids:
2058- quad_element = account_quadruplet_list[0]
2059- return ['&',
2060- '&',
2061- ('general_account_id', '=', quad_element.account_id.id),
2062- ('destination_id', '=', quad_element.account_destination_id.id),
2063- '&',
2064- ('cost_center_id', '=', quad_element.cost_center_id.id),
2065- ('account_id', '=', quad_element.funding_pool_id.id)]
2066- else:
2067- return False
2068- else:
2069- firstElement = self._create_account_quadruplet_domain([account_quadruplet_list[0]], funding_pool_ids)
2070- secondElement = self._create_account_quadruplet_domain(account_quadruplet_list[1:], funding_pool_ids)
2071-
2072- if firstElement and secondElement:
2073- return ['|'] + firstElement + secondElement
2074- elif firstElement:
2075- return firstElement
2076- return secondElement
2077+ def _create_account_quadruplet_domain(self, account_quadruplet_list, funding_pool_ids=None):
2078+ """
2079+ Returns the domain corresponding to the list of quadruplets in param.
2080+ """
2081+ dom = []
2082+ if not account_quadruplet_list:
2083+ return dom
2084+ if funding_pool_ids is None:
2085+ funding_pool_ids = []
2086+ first = True
2087+ for quad in account_quadruplet_list:
2088+ if quad.funding_pool_id.id in funding_pool_ids:
2089+ dom += ['&',
2090+ '&',
2091+ '&',
2092+ ('general_account_id', '=', quad.account_id.id),
2093+ ('destination_id', '=', quad.account_destination_id.id),
2094+ ('cost_center_id', '=', quad.cost_center_id.id),
2095+ ('account_id', '=', quad.funding_pool_id.id)]
2096+ if not first:
2097+ dom.insert(0, '|')
2098+ else:
2099+ first = False
2100+ return dom
2101
2102 def _get_number_of_childs(self, cr, uid, ids, field_name=None, arg=None, context=None):
2103 # Verifications
2104@@ -150,14 +147,27 @@
2105 self.write(cr, uid, ids, {'account_destination_ids':[(6, 0, [])]}, context=context )
2106 return True
2107
2108+ def button_remove_all_accounts(self, cr, uid, ids, context=None):
2109+ """
2110+ Removes all G/L accounts selected in the Reporting lines wizard
2111+ """
2112+ self.write(cr, uid, ids, {'reporting_account_ids': [(6, 0, [])]}, context=context)
2113+ return True
2114+
2115 # Get the list of accounts for both duplet and quadruplet
2116 def _get_accounts_couple_and_quadruplets(self, browse_line):
2117+ """
2118+ Returns a dict with a list of browse records for acc/dest, quadruplets and "accounts only"
2119+ """
2120 account_destination_result = []
2121 account_quadruplet_result = []
2122-
2123+ account_gl_result = []
2124 if browse_line.line_type != 'view':
2125 if browse_line.is_quadruplet:
2126 account_quadruplet_result = [account_quadruplet for account_quadruplet in browse_line.account_quadruplet_ids]
2127+ elif browse_line.reporting_select_accounts_only:
2128+ # this syntax's goal is to get a list of browse records instead of a browse_record_list
2129+ account_gl_result = [a for a in browse_line.reporting_account_ids]
2130 else:
2131 account_destination_result = [account_destination for account_destination in browse_line.account_destination_ids]
2132 else:
2133@@ -165,8 +175,12 @@
2134 temp = self._get_accounts_couple_and_quadruplets(child_line)
2135 account_destination_result += temp['account_destination_list']
2136 account_quadruplet_result += temp['account_quadruplet_list']
2137- return {'account_destination_list': account_destination_result,
2138- 'account_quadruplet_list': account_quadruplet_result}
2139+ account_gl_result += temp['account_gl_list']
2140+ return {
2141+ 'account_destination_list': account_destination_result,
2142+ 'account_quadruplet_list': account_quadruplet_result,
2143+ 'account_gl_list': account_gl_result,
2144+ }
2145
2146 def _get_general_domain(self, cr, uid, browse_format, domain_type, context=None):
2147 # Method to get the domain (allocated or project) of a line
2148@@ -205,6 +219,8 @@
2149 funding_pool_ids = [x for x in funding_pool_ids if x not in fp_ids]
2150 funding_pool_domain = self._create_domain('account_id', funding_pool_ids)
2151 gen_domain['funding_pool_domain'] = funding_pool_domain
2152+ else:
2153+ gen_domain['funding_pool_domain'] = "('account_id', 'in', [])"
2154
2155 gen_domain['funding_pool_ids'] = [x.id for x in funding_pool_ids]
2156 return gen_domain
2157@@ -221,23 +237,35 @@
2158 if format.eligibility_from_date and format.eligibility_to_date:
2159 #### DUY US-385: MOVE THIS TO OUTSIDE OF THE ALL THE LOOPS
2160 general_domain = self._get_general_domain(cr, uid, format, domain_type, context=context)
2161+ accounts_criteria = ['&', '&', ] + non_corrected_domain
2162+ acc_domains = []
2163
2164 # Account + destination domain
2165 account_destination_quadruplet_ids = self._get_accounts_couple_and_quadruplets(browse_line)
2166- account_couple_domain = self._create_account_couple_domain(account_destination_quadruplet_ids['account_destination_list'], False)
2167+ account_couple_domain = self._create_account_couple_domain(account_destination_quadruplet_ids['account_destination_list'])
2168+ if account_couple_domain:
2169+ acc_domains += [account_couple_domain]
2170 # get the criteria for accounts of quadruplet mode
2171 account_quadruplet_domain = self._create_account_quadruplet_domain(account_destination_quadruplet_ids['account_quadruplet_list'], general_domain['funding_pool_ids'])
2172-
2173- if not account_couple_domain and not account_quadruplet_domain:
2174- return [('id', '=', '-1')]
2175-
2176- accounts_criteria = ['&', '&', ] + non_corrected_domain
2177- if account_couple_domain and account_quadruplet_domain:
2178- accounts_criteria += ['|'] + account_couple_domain + account_quadruplet_domain
2179- elif account_couple_domain:
2180- accounts_criteria += account_couple_domain
2181- elif account_quadruplet_domain:
2182- accounts_criteria += account_quadruplet_domain
2183+ if account_quadruplet_domain:
2184+ acc_domains += [account_quadruplet_domain]
2185+ # "Accounts Only" Domain
2186+ account_only_domain = []
2187+ if account_destination_quadruplet_ids['account_gl_list']:
2188+ acc_ids = [a.id for a in account_destination_quadruplet_ids['account_gl_list']]
2189+ account_only_domain = [('general_account_id', 'in', acc_ids)]
2190+ if account_only_domain:
2191+ acc_domains += [account_only_domain]
2192+
2193+ # note: it's possible to have more than one domain in case several lines are grouped into a view
2194+ if len(acc_domains) == 1:
2195+ accounts_criteria += acc_domains[0]
2196+ elif len(acc_domains) == 2:
2197+ accounts_criteria += ['|'] + acc_domains[0] + acc_domains[1]
2198+ elif len(acc_domains) == 3:
2199+ accounts_criteria += ['|'] + ['|'] + acc_domains[0] + acc_domains[1] + acc_domains[2]
2200+ else:
2201+ return [('id', '=', -1)]
2202
2203 return accounts_criteria
2204 else:
2205@@ -404,6 +432,67 @@
2206
2207 return res
2208
2209+ def _get_quadruplet_sync_list(self, cr, uid, ids, field_name=None, arg=None, context=None):
2210+ tmp_quad = {}
2211+ link_ids = set()
2212+ aa_ids = set()
2213+
2214+ for line in self.browse(cr, uid, ids, fields_to_fetch=['account_quadruplet_ids'], context=context):
2215+ for quad in line.account_quadruplet_ids:
2216+ link_ids.add(quad.account_destination_link_id.id)
2217+ aa_ids.add(quad.funding_pool_id.id)
2218+ aa_ids.add(quad.cost_center_id.id)
2219+ tmp_quad.setdefault(line.id, []).append([quad.account_destination_link_id.id, quad.funding_pool_id.id, quad.cost_center_id.id])
2220+
2221+ if link_ids:
2222+ link_sdref = self.pool.get('account.destination.link').get_sd_ref(cr, uid, list(link_ids), context=context)
2223+ if aa_ids:
2224+ aa_sdref = self.pool.get('account.analytic.account').get_sd_ref(cr, uid, list(aa_ids), context=context)
2225+
2226+ ret = {}
2227+ for _id in ids:
2228+ ret[_id] = []
2229+ for quad_list in tmp_quad.get(_id, []):
2230+ ret[_id].append([link_sdref.get(quad_list[0]), aa_sdref.get(quad_list[1]), aa_sdref.get(quad_list[2])])
2231+ ret[_id] = '%s' % ret[_id]
2232+ return ret
2233+
2234+
2235+ def _set_quadruplet_sync_list(self, cr, uid, id, name, value, arg, context):
2236+ quad_obj = self.pool.get('financing.contract.account.quadruplet')
2237+
2238+ value_list = safe_eval(value)
2239+ link_ids = set()
2240+ aa_ids = set()
2241+
2242+ cr.execute('delete from financing_contract_actual_account_quadruplets where actual_line_id = %s', (id, ))
2243+ for data in value_list:
2244+ link_ids.add(data[0])
2245+ aa_ids.add(data[1])
2246+ aa_ids.add(data[2])
2247+
2248+ if link_ids:
2249+ link_sdref = self.pool.get('account.destination.link').find_sd_ref(cr, uid, list(link_ids), context=context)
2250+ if aa_ids:
2251+ aa_sdref = self.pool.get('account.analytic.account').find_sd_ref(cr, uid, list(aa_ids), context=context)
2252+
2253+ for data in value_list:
2254+ 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)
2255+ if not quad_id:
2256+ 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
2257+ account_destination_name, account_id, cost_center_id, disabled, account_destination_link_id, funding_pool_id, account_destination_id
2258+ from
2259+ financing_contract_account_quadruplet_view
2260+ where
2261+ account_destination_link_id = %s and
2262+ funding_pool_id = %s and
2263+ cost_center_id = %s
2264+ ) RETURNING id
2265+ ''', (link_sdref.get(data[0]), aa_sdref.get(data[1]), aa_sdref.get(data[2])))
2266+ quad_id = cr.fetchone()
2267+ cr.execute('insert into financing_contract_actual_account_quadruplets (actual_line_id, account_quadruplet_id) values (%s, %s)', (id, quad_id[0]))
2268+
2269+ return True
2270
2271 _columns = {
2272 'name': fields.char('Name', size=64, required=True),
2273@@ -411,7 +500,7 @@
2274 'format_id': fields.many2one('financing.contract.format', 'Format'),
2275 'is_quadruplet': fields.boolean('Input CC/FP at line level?'),
2276 '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']),
2277- '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'),
2278+ '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'),
2279 'parent_id': fields.many2one('financing.contract.format.line', 'Parent line'),
2280 'child_ids': fields.one2many('financing.contract.format.line', 'parent_id', 'Child lines'),
2281 'line_type': fields.selection([('view','View'),
2282@@ -431,12 +520,23 @@
2283
2284 'allocated_real': fields.function(_get_actual_amount, method=True, store=False, string="Funded - Actuals", type="float", readonly=True),
2285 'project_real': fields.function(_get_actual_amount, method=True, store=False, string="Total project - Actuals", type="float", readonly=True),
2286- 'quadruplet_update': fields.text('Internal Use Only'),
2287+ 'quadruplet_update': fields.text('Internal Use Only (deprecated - kept to manage old sync update)'),
2288+ 'quadruplet_sync_list': fields.function(_get_quadruplet_sync_list, method=True, string='Used to sync quad', type='text', fnct_inv=_set_quadruplet_sync_list),
2289 'instance_id': fields.many2one('msf.instance','Proprietary Instance'),
2290+ 'reporting_select_accounts_only': fields.boolean(string="Select Accounts Only"),
2291+ 'reporting_account_ids': fields.many2many('account.account', 'contract_format_line_account_rel', 'format_line_id', 'account_id',
2292+ string='G/L Accounts',
2293+ domain="[('type', '!=', 'view'),"
2294+ " ('is_analytic_addicted', '=', True),"
2295+ " ('active', 'in', ['t', 'f'])]",
2296+ order_by='code'),
2297 }
2298
2299+
2300+
2301 _defaults = {
2302 'is_quadruplet': False,
2303+ 'reporting_select_accounts_only': False,
2304 'line_type': 'actual',
2305 'overhead_type': 'cost_percentage',
2306 'parent_id': lambda *a: False
2307@@ -445,36 +545,46 @@
2308 _order = 'code asc'
2309
2310 # UF-2311: Calculate the quadruplet value before writing or creating the format line
2311- def calculate_quaduplet(self, vals, context):
2312+ def calculate_quadruplet(self, cr, uid, vals, context):
2313+ # View Line Type = no items selected
2314 if 'line_type' in vals and vals['line_type'] == 'view':
2315 vals['allocated_amount'] = 0.0
2316 vals['project_amount'] = 0.0
2317 vals['account_destination_ids'] = [(6, 0, [])]
2318 vals['account_quadruplet_ids'] = [(6, 0, [])]
2319- elif 'is_quadruplet' in vals: # If the vals contains quadruplet value, then check if it is true or false
2320- if vals.get('is_quadruplet', False):
2321- # delete account/destinations
2322- vals['account_destination_ids'] = [(6, 0, [])]
2323- if context.get('sync_update_execution'):
2324- quads_list = []
2325- if vals.get('quadruplet_update', False):
2326- quadrup_str = vals['quadruplet_update']
2327- quads_list = map(int, quadrup_str.split(','))
2328- vals['account_quadruplet_ids'] = [(6, 0, quads_list)]
2329- else:
2330- temp = vals['account_quadruplet_ids']
2331- if temp[0]:
2332- vals['quadruplet_update'] = str(temp[0][2]).strip('[]')
2333- else:
2334- vals['account_quadruplet_ids'] = [(6, 0, [])]
2335- vals['quadruplet_update'] = '' # delete quadruplets
2336+ vals['quadruplet_update'] = ''
2337+ vals['reporting_account_ids'] = [(6, 0, [])]
2338+ vals['is_quadruplet'] = False
2339+ vals['reporting_select_accounts_only'] = False
2340+ # "Input CC/FP at line level" = quadruplets selected
2341+ elif vals.get('is_quadruplet'):
2342+ # reset the acc/dest and G/L accounts which might have been selected before ticking the box "Input CC/FP at line level"
2343+ vals['account_destination_ids'] = [(6, 0, [])]
2344+ vals['reporting_account_ids'] = [(6, 0, [])]
2345+ if context.get('sync_update_execution') and vals.get('quadruplet_update', False) and 'quadruplet_sync_list' not in vals:
2346+ # old sync update received
2347+ quadrup_str = vals['quadruplet_update']
2348+ quads_list = map(int, quadrup_str.split(','))
2349+ vals['account_quadruplet_ids'] = [(6, 0, self.pool.get('financing.contract.account.quadruplet').migrate_old_quad(cr, uid, quads_list))]
2350+ # "Select Accounts Only" = only G/L accounts selected: reset the acc/dest and quadruplets
2351+ elif vals.get('reporting_select_accounts_only'):
2352+ vals['account_destination_ids'] = [(6, 0, [])]
2353+ vals['account_quadruplet_ids'] = [(6, 0, [])]
2354+ vals['quadruplet_update'] = ''
2355+ # No boxes ticked = Accounts/Destinations selected: reset the G/L accounts and quadruplets
2356+ elif 'is_quadruplet' in vals and 'reporting_select_accounts_only' in vals and \
2357+ not vals['is_quadruplet'] and not vals['reporting_select_accounts_only']:
2358+ vals['reporting_account_ids'] = [(6, 0, [])]
2359+ vals['account_quadruplet_ids'] = [(6, 0, [])]
2360+ vals['quadruplet_update'] = ''
2361+
2362
2363 def create(self, cr, uid, vals, context=None):
2364 if not context:
2365 context = {}
2366
2367 # calculate the quadruplet combination
2368- self.calculate_quaduplet(vals, context)
2369+ self.calculate_quadruplet(cr, uid, vals, context)
2370 return super(financing_contract_format_line, self).create(cr, uid, vals, context=context)
2371
2372 def write(self, cr, uid, ids, vals, context=None):
2373@@ -493,7 +603,7 @@
2374 return True
2375
2376 # calculate the quadruplet combination
2377- self.calculate_quaduplet(vals, context)
2378+ self.calculate_quadruplet(cr, uid, vals, context)
2379 return super(financing_contract_format_line, self).write(cr, uid, ids, vals, context=context)
2380
2381 def copy_format_line(self, cr, uid, browse_source_line, destination_format_id, parent_id=None, context=None):
2382@@ -505,6 +615,7 @@
2383 'parent_id': parent_id,
2384 'line_type': browse_source_line.line_type,
2385 'account_quadruplet_ids': [(6, 0, [])],
2386+ 'reporting_account_ids': [(6, 0, [])],
2387 }
2388 account_destination_ids = [account_destination.id for account_destination in browse_source_line.account_destination_ids]
2389 format_line_vals['account_destination_ids'] = [(6, 0, account_destination_ids)]
2390@@ -513,6 +624,25 @@
2391 self.copy_format_line(cr, uid, child_line, destination_format_id, parent_line_id, context=context)
2392 return
2393
2394+ def on_change_is_quadruplet(self, cr, uid, ids, is_quadruplet, context=None):
2395+ """
2396+ Ticking "Input CC/FP at line level?" automatically unticks "Select Accounts Only"
2397+ """
2398+ res = {}
2399+ if is_quadruplet:
2400+ res['value'] = {'reporting_select_accounts_only': False, }
2401+ return res
2402+
2403+ def on_change_reporting_select_accounts_only(self, cr, uid, ids, reporting_select_accounts_only, context=None):
2404+ """
2405+ Ticking "Select Accounts Only" automatically unticks "Input CC/FP at line level?"
2406+ """
2407+ res = {}
2408+ if reporting_select_accounts_only:
2409+ res['value'] = {'is_quadruplet': False, }
2410+ return res
2411+
2412+
2413 financing_contract_format_line()
2414
2415
2416
2417=== modified file 'bin/addons/financing_contract/report/financing_contract.py'
2418--- bin/addons/financing_contract/report/financing_contract.py 2016-02-26 10:24:24 +0000
2419+++ bin/addons/financing_contract/report/financing_contract.py 2020-11-02 12:53:07 +0000
2420@@ -74,6 +74,10 @@
2421 str(quad.funding_pool_id.code),
2422 str(quad.cost_center_id.code)]),
2423 account_list, account_list_index)
2424+ elif line.reporting_account_ids:
2425+ # G/L Accounts Only selected
2426+ for account in line.reporting_account_ids:
2427+ account_list_index = add_account_list_block_item(str(account.code), account_list, account_list_index)
2428 else:
2429 # Case where we have some destination_ids
2430 for account_destination in line.account_destination_ids:
2431
2432=== modified file 'bin/addons/financing_contract/report/report_project_expenses.py'
2433--- bin/addons/financing_contract/report/report_project_expenses.py 2019-10-30 16:33:21 +0000
2434+++ bin/addons/financing_contract/report/report_project_expenses.py 2020-11-02 12:53:07 +0000
2435@@ -1,6 +1,8 @@
2436 from report import report_sxw
2437 from spreadsheet_xml.spreadsheet_xml_write import SpreadsheetReport
2438 from tools.translate import _
2439+import logging
2440+
2441 assert _ # pyflakes check
2442
2443 class report_project_expenses2(report_sxw.rml_parse):
2444@@ -17,7 +19,6 @@
2445 self.lines = {}
2446 self.totalRptCurrency = 0
2447 self.totalBookAmt = 0
2448- self.iter = []
2449 self.localcontext.update({
2450 'getLines':self.getLines,
2451 'getCostCenter':self.getCostCenter,
2452@@ -26,29 +27,12 @@
2453 'getSub1':self.getSub1,
2454 'getSub2':self.getSub2,
2455 'getLines2':self.getLines2,
2456- 'getFormula':self.getFormula,
2457 'totalRptCurrency': self.totalRptCurrency,
2458 'totalBookAmt':self.totalBookAmt,
2459 'getTotalRptCurrency': self.getTotalRptCurrency,
2460 'getTotalBookAmt': self.getTotalBookAmt,
2461 })
2462
2463- def getFormula(self):
2464- formul = ''
2465- iters = self.iter[1:]
2466- temp = self.iter[1:]
2467- tour = 1
2468- for i in temp:
2469- tour += 1
2470- nb = 0
2471- for x in iters:
2472- nb += x + 1
2473- rang = nb + 1
2474- formul += '+R[-'+str(rang)+']C'
2475- iters = self.iter[tour:]
2476-
2477- return self.totalRptCurrency
2478- return formul
2479
2480 def getTotalBookAmt(self):
2481 return self.totalBookAmt
2482@@ -100,51 +84,46 @@
2483 return []
2484 contract_obj = self.pool.get('financing.contract.contract')
2485 format_line_obj = self.pool.get('financing.contract.format.line')
2486+ logger = logging.getLogger('contract.report')
2487+
2488 contract_domain = contract_obj.get_contract_domain(self.cr, self.uid, contract, reporting_type=self.reporting_type)
2489 analytic_line_obj = self.pool.get('account.analytic.line')
2490 analytic_lines = analytic_line_obj.search(self.cr, self.uid, contract_domain, context=None)
2491
2492- # list of analytic journal_ids which are in the engagement journals
2493+ # list of analytic journal_ids which are in the engagement journals: to be added in get_contract_domain ?
2494 exclude_journal_ids = self.pool.get('account.analytic.journal').search(self.cr, self.uid, [('type','=','engagement')])
2495- exclude_line_ids = []
2496+
2497+ # gen a dict to store aji cond = reporting_line.code, reporting_line.name
2498+ line_code_name_by_cond = {}
2499+ reporting_lines_id = format_line_obj.search(self.cr, self.uid, [('format_id', '=', contract.format_id.id), ('line_type', '!=', 'view')])
2500+ for report_line in format_line_obj.browse(self.cr, self.uid, reporting_lines_id):
2501+ if report_line.is_quadruplet:
2502+ for quad in report_line.account_quadruplet_ids:
2503+ 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)
2504+ elif not report_line.reporting_select_accounts_only:
2505+ for tiplet in report_line.account_destination_ids:
2506+ line_code_name_by_cond[(tiplet.account_id.id, tiplet.destination_id.id)] = (report_line.code, report_line.name)
2507+ else:
2508+ for gl_only in report_line.reporting_account_ids:
2509+ line_code_name_by_cond[gl_only.id] = (report_line.code, report_line.name)
2510+
2511+ # iterate over aji, to link each aji to its reporting_line
2512 for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2513 if analytic_line.journal_id.id in exclude_journal_ids:
2514- exclude_line_ids.append(analytic_line.id)
2515- analytic_lines = [x for x in analytic_lines if x not in exclude_line_ids]
2516-
2517- # UFTP-16: First search in the triplet in format line, then in the second block below, search in quadruplet
2518- for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2519- 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) ])
2520- ids_fcfl = format_line_obj.search(self.cr, self.uid, [('account_destination_ids','in',ids_adl), ('format_id', '=', contract.format_id.id)])
2521- for fcfl in format_line_obj.browse(self.cr, self.uid, ids_fcfl):
2522- ana_tuple = (analytic_line, fcfl.code, fcfl.name)
2523- if lines.has_key(fcfl.code):
2524- if not ana_tuple in lines[fcfl.code]:
2525- lines[fcfl.code] += [ana_tuple]
2526- else:
2527- lines[fcfl.code] = [ana_tuple]
2528-
2529- # UFTP-16: First search in the triplet in format line, then in the second block below, search in quadruplet
2530- for analytic_line in analytic_line_obj.browse(self.cr, self.uid, analytic_lines, context=None):
2531- # US-460: Include also the funding pool in the criteria when searching for the quadruplet of the contract line
2532- criteria_for_adl = [('account_id', '=', analytic_line.general_account_id.id),
2533- ('account_destination_id', '=', analytic_line.destination_id and analytic_line.destination_id.id or False),
2534- ('funding_pool_id', '=', analytic_line.account_id.id),
2535- ('cost_center_id', '=', analytic_line.cost_center_id and analytic_line.cost_center_id.id or False)]
2536- ids_adl = self.pool.get('financing.contract.account.quadruplet').search(self.cr, self.uid, criteria_for_adl)
2537-
2538- ids_fcfl = format_line_obj.search(self.cr, self.uid, [('account_quadruplet_ids','in',ids_adl), ('format_id', '=', contract.format_id.id)])
2539- for fcfl in format_line_obj.browse(self.cr, self.uid, ids_fcfl):
2540- ana_tuple = (analytic_line, fcfl.code, fcfl.name)
2541- if lines.has_key(fcfl.code):
2542- if not ana_tuple in lines[fcfl.code]:
2543- lines[fcfl.code] += [ana_tuple]
2544- else:
2545- lines[fcfl.code] = [ana_tuple]
2546+ continue
2547+ quad_key = (analytic_line.general_account_id.id, analytic_line.destination_id.id, analytic_line.cost_center_id.id, analytic_line.account_id.id)
2548+ if quad_key in line_code_name_by_cond:
2549+ 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]))
2550+ elif quad_key[0:2] in line_code_name_by_cond:
2551+ tiplet_key = quad_key[0:2]
2552+ lines.setdefault(line_code_name_by_cond[tiplet_key], []).append((analytic_line, line_code_name_by_cond[tiplet_key][0], line_code_name_by_cond[tiplet_key][1]))
2553+ elif quad_key[0] in line_code_name_by_cond:
2554+ gl_key = quad_key[0]
2555+ 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]))
2556+ else:
2557+ 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))
2558
2559 self.lines = lines
2560- for x in lines:
2561- self.iter.append(len(lines[x]))
2562 return lines
2563
2564
2565
2566=== modified file 'bin/addons/msf_audittrail/audittrail_invoice_data.yml'
2567--- bin/addons/msf_audittrail/audittrail_invoice_data.yml 2019-06-26 10:13:00 +0000
2568+++ bin/addons/msf_audittrail/audittrail_invoice_data.yml 2020-11-02 12:53:07 +0000
2569@@ -259,7 +259,7 @@
2570 # Create the rule
2571 fields = ['state', 'category', 'code', 'complete_name', 'cost_center_ids', 'date', 'date_start', 'name', 'parent_id',
2572 'type', 'for_fx_gain_loss', 'instance_id', 'tuple_destination_account_ids', 'description', 'destination_ids',
2573- 'dest_cc_ids', 'allow_all_cc']
2574+ 'dest_cc_ids', 'allow_all_cc', 'allow_all_cc_with_fp', 'select_accounts_only', 'fp_account_ids']
2575
2576 fields_ids = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=' ,'account.analytic.account'), ('name', 'in', fields)], context=context)
2577
2578
2579=== modified file 'bin/addons/msf_doc_import/account.py'
2580--- bin/addons/msf_doc_import/account.py 2020-06-25 08:27:13 +0000
2581+++ bin/addons/msf_doc_import/account.py 2020-11-02 12:53:07 +0000
2582@@ -180,10 +180,6 @@
2583 # Prepare some values
2584 # Do changes because of YAML tests
2585 cr = pooler.get_db(dbname).cursor()
2586- try:
2587- msf_fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2588- except ValueError:
2589- msf_fp_id = 0
2590 created = 0
2591 processed = 0
2592 errors = []
2593@@ -517,23 +513,15 @@
2594 errors.append(_('Line %s. The Cost Center %s is not compatible with the Destination %s.') %
2595 (current_line_num, line[cols['Cost Centre']], line[cols['Destination']]))
2596 continue
2597- # if the Fund. Pool used is NOT "PF" check the compatibility with the (account, dest) and the CC
2598- if r_fp != msf_fp_id:
2599- fp_fields = ['tuple_destination_account_ids', 'cost_center_ids']
2600- fp = self.pool.get('account.analytic.account').browse(cr, uid, r_fp,
2601- fields_to_fetch=fp_fields, context=context)
2602- if (account.id, r_destination) not in \
2603- [t.account_id and t.destination_id and (t.account_id.id, t.destination_id.id)
2604- for t in fp.tuple_destination_account_ids if not t.disabled]:
2605- errors.append(_('Line %s. The combination "account %s and destination %s" is not '
2606- 'compatible with the Funding Pool %s.') %
2607- (current_line_num, line[cols['G/L Account']], line[cols['Destination']],
2608- line[cols['Funding Pool']]))
2609- continue
2610- if cc.id not in [c.id for c in fp.cost_center_ids]:
2611- errors.append(_('Line %s. The Cost Center %s is not compatible with the Funding Pool %s.') %
2612- (current_line_num, line[cols['Cost Centre']], line[cols['Funding Pool']]))
2613- continue
2614+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, r_fp, account.id, r_destination, context=context):
2615+ errors.append(_('Line %s. The combination "account %s and destination %s" is not '
2616+ 'compatible with the Funding Pool %s.') %
2617+ (current_line_num, line[cols['G/L Account']], line[cols['Destination']], line[cols['Funding Pool']]))
2618+ continue
2619+ if not ad_obj.check_fp_cc_compatibility(cr, uid, r_fp, cc.id, context=context):
2620+ errors.append(_('Line %s. The Cost Center %s is not compatible with the Funding Pool %s.') %
2621+ (current_line_num, line[cols['Cost Centre']], line[cols['Funding Pool']]))
2622+ continue
2623
2624 # US-937: use period of import file
2625 if period_name.startswith('Period 16'):
2626
2627=== modified file 'bin/addons/msf_homere_interface/hr.py'
2628--- bin/addons/msf_homere_interface/hr.py 2020-09-01 08:22:26 +0000
2629+++ bin/addons/msf_homere_interface/hr.py 2020-11-02 12:53:07 +0000
2630@@ -209,21 +209,26 @@
2631 (_check_unicity, "Another employee has the same Identification No.", ['identification_id']),
2632 ]
2633
2634- def _check_employe_dest_cc_compatibility(self, cr, uid, employee_id, context=None):
2635+ def _check_employee_cc_compatibility(self, cr, uid, employee_id, context=None):
2636 """
2637- Raises an error in case the employee Destination and Cost Center are not compatible
2638+ Raises an error in case the employee "Destination and Cost Center" or "Funding Pool and Cost Center" are not compatible.
2639 """
2640 if context is None:
2641 context = {}
2642 ad_obj = self.pool.get('analytic.distribution')
2643- employee_fields = ['destination_id', 'cost_center_id', 'name_resource']
2644+ employee_fields = ['destination_id', 'cost_center_id', 'funding_pool_id', 'name_resource']
2645 employee = self.browse(cr, uid, employee_id, fields_to_fetch=employee_fields, context=context)
2646 emp_dest = employee.destination_id
2647 emp_cc = employee.cost_center_id
2648+ emp_fp = employee.funding_pool_id
2649 if emp_dest and emp_cc:
2650 if not ad_obj.check_dest_cc_compatibility(cr, uid, emp_dest.id, emp_cc.id, context=context):
2651 raise osv.except_osv(_('Error'), _('Employee %s: the Cost Center %s is not compatible with the Destination %s.') %
2652 (employee.name_resource, emp_cc.code or '', emp_dest.code or ''))
2653+ if emp_fp and emp_cc:
2654+ if not ad_obj.check_fp_cc_compatibility(cr, uid, emp_fp.id, emp_cc.id, context=context):
2655+ raise osv.except_osv(_('Error'), _('Employee %s: the Cost Center %s is not compatible with the Funding Pool %s.') %
2656+ (employee.name_resource, emp_cc.code or '', emp_fp.code or ''))
2657
2658 def create(self, cr, uid, vals, context=None):
2659 """
2660@@ -246,7 +251,7 @@
2661 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:
2662 raise osv.except_osv(_('Error'), _('You are not allowed to create a local staff! Please use Import to create local staff.'))
2663 employee_id = super(hr_employee, self).create(cr, uid, vals, context)
2664- self._check_employe_dest_cc_compatibility(cr, uid, employee_id, context=context)
2665+ self._check_employee_cc_compatibility(cr, uid, employee_id, context=context)
2666 return employee_id
2667
2668 def write(self, cr, uid, ids, vals, context=None):
2669@@ -298,7 +303,7 @@
2670 employee_id = super(hr_employee, self).write(cr, uid, emp.id, new_vals, context)
2671 if employee_id:
2672 res.append(employee_id)
2673- self._check_employe_dest_cc_compatibility(cr, uid, emp.id, context=context)
2674+ self._check_employee_cc_compatibility(cr, uid, emp.id, context=context)
2675 return res
2676
2677 def unlink(self, cr, uid, ids, context=None):
2678@@ -328,7 +333,7 @@
2679
2680 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2681 """
2682- Change funding pool domain in order to include MSF Private fund
2683+ Adapts domain for AD fields
2684 """
2685 if not context:
2686 context = {}
2687@@ -350,36 +355,16 @@
2688 dest_field.set('domain', "[('category', '=', 'DEST'), ('type', '!=', 'view'), "
2689 "('dest_compatible_with_cc_ids', '=', cost_center_id)]")
2690 # Change FP field
2691- try:
2692- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2693- except ValueError:
2694- fp_id = 0
2695- fp_fields = form.xpath('/' + view_type + '//field[@name="funding_pool_id"]')
2696+ fp_fields = form.xpath('/' + view_type + '//field[@name="funding_pool_id"]')
2697 for field in fp_fields:
2698- field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('state', '=', 'open'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
2699+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
2700+ "('fp_compatible_with_cc_ids', '=', cost_center_id)]")
2701 view['arch'] = etree.tostring(form)
2702 return view
2703
2704 def onchange_cc(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
2705- """
2706- Update FP or CC regarding both.
2707- """
2708- # Prepare some values
2709- vals = {}
2710- if not cost_center_id or not funding_pool_id:
2711- return {}
2712- if cost_center_id and funding_pool_id:
2713- fp = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2714- try:
2715- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2716- except ValueError:
2717- fp_id = 0
2718- # Exception for MSF Private Fund
2719- if funding_pool_id == fp_id:
2720- return {}
2721- if cost_center_id not in [x.id for x in fp.cost_center_ids]:
2722- vals.update({'funding_pool_id': False})
2723- return {'value': vals}
2724+ return self.pool.get('analytic.distribution').\
2725+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
2726
2727 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
2728
2729
2730=== modified file 'bin/addons/msf_homere_interface/hr_payroll.py'
2731--- bin/addons/msf_homere_interface/hr_payroll.py 2019-05-14 15:12:35 +0000
2732+++ bin/addons/msf_homere_interface/hr_payroll.py 2020-11-02 12:53:07 +0000
2733@@ -103,7 +103,7 @@
2734 continue
2735 if line.funding_pool_id and not line.destination_id: # CASE 2/
2736 # D Check, except B check
2737- 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:
2738+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.funding_pool_id.id, line.cost_center_id.id, context=context):
2739 res[line.id] = 'invalid'
2740 continue
2741 elif not line.funding_pool_id and line.destination_id: # CASE 3/
2742@@ -114,11 +114,12 @@
2743 continue
2744 else: # CASE 4/
2745 # C Check, except B
2746- 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:
2747+ if not ad_obj.check_fp_acc_dest_compatibility(cr, uid, line.funding_pool_id.id, line.account_id.id,
2748+ line.destination_id.id, context=context):
2749 res[line.id] = 'invalid'
2750 continue
2751 # D Check, except B check
2752- 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:
2753+ if not ad_obj.check_fp_cc_compatibility(cr, uid, line.funding_pool_id.id, line.cost_center_id.id, context=context):
2754 res[line.id] = 'invalid'
2755 continue
2756 # E Check
2757@@ -163,9 +164,9 @@
2758 if isinstance(ids, (int, long)):
2759 ids = [ids]
2760
2761- fp = [0]
2762- cc = [0]
2763- dest = [0]
2764+ fp = [-1]
2765+ cc = [-1]
2766+ dest = [-1]
2767 for ana_account in self.read(cr, uid, ids, ['category']):
2768 if ana_account['category'] == 'OC':
2769 cc.append(ana_account['id'])
2770@@ -174,8 +175,13 @@
2771 elif ana_account['category'] == 'FUNDING':
2772 fp.append(ana_account['id'])
2773 if len(fp) > 1 or len(cc) > 1 or len(dest) > 1:
2774- 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)])
2775-
2776+ return self.pool.get('hr.payroll.msf').search(cr, uid,
2777+ [('state', '=', 'draft'),
2778+ '|', '|',
2779+ ('funding_pool_id', 'in', fp),
2780+ ('cost_center_id', 'in', cc),
2781+ ('destination_id', 'in', dest)],
2782+ order='NO_ORDER')
2783 return []
2784
2785 def _get_trigger_state_account(self, cr, uid, ids, context=None):
2786@@ -235,7 +241,12 @@
2787 store={
2788 'hr.payroll.msf': (lambda self, cr, uid, ids, c=None: ids, ['account_id', 'cost_center_id', 'funding_pool_id', 'destination_id'], 10),
2789 'account.account': (_get_trigger_state_account, ['user_type_code', 'destination_ids'], 20),
2790- 'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'cost_center_ids', 'tuple_destination_account_ids'], 20),
2791+ 'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'allow_all_cc',
2792+ 'dest_cc_ids', 'allow_all_cc_with_fp',
2793+ 'cost_center_ids', 'select_accounts_only',
2794+ 'fp_account_ids',
2795+ 'tuple_destination_account_ids'],
2796+ 20),
2797 'account.destination.link': (_get_trigger_state_dest_link, ['account_id', 'destination_id'], 30),
2798 }
2799 ),
2800@@ -255,7 +266,7 @@
2801
2802 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2803 """
2804- Change funding pool domain in order to include MSF Private fund
2805+ Adapts domain for AD fields
2806 """
2807 if not context:
2808 context = {}
2809@@ -272,13 +283,11 @@
2810 for field in fields:
2811 field.set('domain', "[('category', '=', 'OC'), ('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
2812 # Change FP field
2813- try:
2814- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2815- except ValueError:
2816- fp_id = 0
2817 fp_fields = form.xpath('//field[@name="funding_pool_id"]')
2818 for field in fp_fields:
2819- 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)
2820+ field.set('domain', "[('category', '=', 'FUNDING'), ('type', '!=', 'view'), "
2821+ "('fp_compatible_with_cc_ids', '=', cost_center_id), "
2822+ "('fp_compatible_with_acc_dest_ids', '=', (account_id, destination_id))]")
2823 # Change Destination field
2824 dest_fields = form.xpath('//field[@name="destination_id"]')
2825 for field in dest_fields:
2826@@ -288,32 +297,8 @@
2827 return view
2828
2829 def onchange_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False):
2830- """
2831- Check given funding pool with destination
2832- """
2833- # Prepare some values
2834- res = {}
2835- # If all elements given, then search FP compatibility
2836- if destination_id and funding_pool_id and account_id:
2837- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2838- # Search MSF Private Fund element, because it's valid with all accounts
2839- try:
2840- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
2841- 'analytic_account_msf_private_funds')[1]
2842- except ValueError:
2843- fp_id = 0
2844- # Delete funding_pool_id if not valid with tuple "account_id/destination_id".
2845- # but do an exception for MSF Private FUND analytic account
2846- 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:
2847- res = {'value': {'funding_pool_id': False}}
2848- # If no destination, do nothing
2849- elif not destination_id:
2850- res = {}
2851- # Otherway: delete FP
2852- else:
2853- res = {'value': {'funding_pool_id': False}}
2854- # If destination given, search if given
2855- return res
2856+ return self.pool.get('analytic.distribution').\
2857+ onchange_ad_destination(cr, uid, ids, destination_id=destination_id, funding_pool_id=funding_pool_id, account_id=account_id)
2858
2859 def create(self, cr, uid, vals, context=None):
2860 """
2861
2862=== modified file 'bin/addons/msf_homere_interface/hr_payroll_wizard.xml'
2863--- bin/addons/msf_homere_interface/hr_payroll_wizard.xml 2020-02-07 15:20:55 +0000
2864+++ bin/addons/msf_homere_interface/hr_payroll_wizard.xml 2020-11-02 12:53:07 +0000
2865@@ -18,7 +18,9 @@
2866 <field name="destination_id" context="{'search_default_active': 1, 'hide_inactive': 1}"
2867 domain="[('category', '=', 'DEST'), ('type', '!=', 'view'),
2868 ('dest_compatible_with_cc_ids', '=', cost_center_id)]"/>
2869- <field name="funding_pool_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
2870+ <field name="funding_pool_id" context="{'search_default_active': 1, 'hide_inactive': 1}"
2871+ domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'),
2872+ ('fp_compatible_with_cc_ids', '=', cost_center_id)]"/>
2873 </group>
2874 <newline/>
2875 <field name="free1_id" context="{'search_default_active': 1, 'hide_inactive': 1}"/>
2876
2877=== modified file 'bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py'
2878--- bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py 2015-07-22 09:03:35 +0000
2879+++ bin/addons/msf_homere_interface/wizard/hr_analytic_reallocation.py 2020-11-02 12:53:07 +0000
2880@@ -40,7 +40,7 @@
2881
2882 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2883 """
2884- Change funding pool domain in order to include MSF Private fund
2885+ Computes the domain for the Cost Center field
2886 """
2887 if not context:
2888 context = {}
2889@@ -56,41 +56,13 @@
2890 fields = form.xpath('//field[@name="cost_center_id"]')
2891 for field in fields:
2892 field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
2893- # Change FP field
2894- try:
2895- fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
2896- except ValueError:
2897- fp_id = 0
2898- fp_fields = form.xpath('//field[@name="funding_pool_id"]')
2899- # Do not use line with account_id, because of NO ACCOUNT_ID PRESENCE!
2900- for field in fp_fields:
2901- field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', ('cost_center_ids', '=', cost_center_id), ('id', '=', %s)]" % fp_id)
2902- # NO NEED TO CHANGE DESTINATION_ID FIELD because NO ACCOUNT_ID PRESENCE!
2903 # Apply changes
2904 view['arch'] = etree.tostring(form)
2905 return view
2906
2907 def onchange_cost_center(self, cr, uid, ids, cost_center_id=False, funding_pool_id=False):
2908- """
2909- Check given cost_center with funding pool
2910- """
2911- # Prepare some values
2912- res = {}
2913- if cost_center_id and funding_pool_id:
2914- fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
2915- # Search MSF Private Fund element, because it's valid with all accounts
2916- try:
2917- fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
2918- 'analytic_account_msf_private_funds')[1]
2919- except ValueError:
2920- fp_id = 0
2921- if cost_center_id not in [x.id for x in fp_line.cost_center_ids] and funding_pool_id != fp_id:
2922- res = {'value': {'funding_pool_id': False}}
2923- elif not cost_center_id:
2924- res = {}
2925- else:
2926- res = {'value': {'funding_pool_id': False}}
2927- return res
2928+ return self.pool.get('analytic.distribution').\
2929+ onchange_ad_cost_center(cr, uid, ids, cost_center_id=cost_center_id, funding_pool_id=funding_pool_id)
2930
2931 def button_validate(self, cr, uid ,ids, context=None):
2932 """
2933
2934=== modified file 'bin/addons/msf_instance/account_target_costcenter.py'
2935--- bin/addons/msf_instance/account_target_costcenter.py 2019-05-14 07:28:06 +0000
2936+++ bin/addons/msf_instance/account_target_costcenter.py 2020-11-02 12:53:07 +0000
2937@@ -27,8 +27,8 @@
2938 _trace = True
2939
2940 _columns = {
2941- 'instance_id': fields.many2one('msf.instance', 'Instance', required=True),
2942- 'cost_center_id': fields.many2one('account.analytic.account', 'Code', domain=[('category', '=', 'OC')], required=True),
2943+ 'instance_id': fields.many2one('msf.instance', 'Instance', required=True, select=1),
2944+ 'cost_center_id': fields.many2one('account.analytic.account', 'Code', domain=[('category', '=', 'OC')], required=True, select=1),
2945 'cost_center_name': fields.related('cost_center_id', 'name', string="Name", readonly=True, type="text"),
2946 'is_target': fields.boolean('Is target'),
2947 'is_top_cost_center': fields.boolean('Top cost centre for budget consolidation'),
2948
2949=== modified file 'bin/addons/msf_profile/data/patches.xml'
2950--- bin/addons/msf_profile/data/patches.xml 2020-10-09 13:44:47 +0000
2951+++ bin/addons/msf_profile/data/patches.xml 2020-11-02 12:53:07 +0000
2952@@ -610,5 +610,9 @@
2953 <field name="method">us_2725_uf_write_date_on_products</field>
2954 </record>
2955
2956+ <record id="us_7243_migrate_contract_quad" model="patch.scripts">
2957+ <field name="method">us_7243_migrate_contract_quad</field>
2958+ </record>
2959+
2960 </data>
2961 </openerp>
2962
2963=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
2964--- bin/addons/msf_profile/i18n/fr_MF.po 2020-10-28 17:16:11 +0000
2965+++ bin/addons/msf_profile/i18n/fr_MF.po 2020-11-02 12:53:07 +0000
2966@@ -3460,7 +3460,7 @@
2967 #: view:financing.contract.account.quadruplet:0
2968 #: view:financing.contract.contract:0
2969 msgid "Account/Destination/Funding Pool/Cost Centre"
2970-msgstr "Account/Destination/Funding Pool/Cost Centre"
2971+msgstr "Compte/Destination/Funding Pool/Centre de Coût"
2972
2973 #. module: sync_client
2974 #: field:sync.monitor,data_push_send:0
2975@@ -8938,7 +8938,7 @@
2976 #. module: financing_contract
2977 #: view:financing.contract.contract:0
2978 msgid "Remove all couples"
2979-msgstr "Remove all couples"
2980+msgstr "Supprimer toutes les paires"
2981
2982 #. modules: purchase, tender_flow, msf_outgoing, stock
2983 #: view:stock.picking:0
2984@@ -15941,7 +15941,7 @@
2985 #. module: financing_contract
2986 #: view:financing.contract.contract:0
2987 msgid "Remove all quads"
2988-msgstr "Remove all quads"
2989+msgstr "Supprimer tous les quadruplets"
2990
2991 #. 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
2992 #: view:account.account:0
2993@@ -16349,8 +16349,14 @@
2994
2995 #. module: analytic_distribution
2996 #: view:account.analytic.account:0
2997-msgid "Remove all"
2998-msgstr "Tout supprimer"
2999+msgid "Remove all accounts/destinations"
3000+msgstr "Supprimer tous les comptes/destinations"
3001+
3002+#. modules: analytic_distribution, financing_contract
3003+#: view:account.analytic.account:0
3004+#: view:financing.contract.contract:0
3005+msgid "Remove all accounts"
3006+msgstr "Supprimer tous les comptes"
3007
3008 #. module: vertical_integration
3009 #: code:addons/vertical_integration/report/hq_report_oca.py:176
3010@@ -28883,8 +28889,9 @@
3011 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\"."
3012 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\"."
3013
3014-#. module: financing_contract
3015+#. modules: financing_contract, analytic_distribution
3016 #: field:financing.contract.format.line,account_destination_ids:0
3017+#: view:account.analytic.account:0
3018 msgid "Accounts/Destinations"
3019 msgstr "Comptes/Destinations"
3020
3021@@ -53406,8 +53413,8 @@
3022
3023 #. module: financing_contract
3024 #: field:financing.contract.format.line,quadruplet_update:0
3025-msgid "Internal Use Only"
3026-msgstr "Internal Use Only"
3027+msgid "Internal Use Only (deprecated - kept to manage old sync update)"
3028+msgstr "Usage Interne Uniquement (déprécié - conservé pour gérer les anciennes mises à jour via synch.)"
3029
3030 #. modules: base_setup, unifield_setup, base
3031 #: selection:base.setup.company,country_id:0
3032@@ -60385,6 +60392,61 @@
3033 msgid "G/L Account"
3034 msgstr "Compte Grand Livre"
3035
3036+#. modules: analytic_distribution, account, analytic_override, financing_contract
3037+#: view:account.analytic.account:0
3038+#: view:account.account:0
3039+#: field:account.analytic.account,fp_account_ids:0
3040+#: view:financing.contract.contract:0
3041+#: field:financing.contract.format.line,reporting_account_ids:0
3042+msgid "G/L Accounts"
3043+msgstr "Comptes Grand Livre"
3044+
3045+#. module: analytic_distribution
3046+#: report:funding.pool:0
3047+msgid "G/L Accounts:"
3048+msgstr "Comptes Grand Livre :"
3049+
3050+#. module: analytic_override
3051+#: help:account.analytic.account,fp_account_ids:0
3052+msgid "G/L accounts linked to the Funding Pool"
3053+msgstr "Comptes Grand Livre liés au Funding Pool"
3054+
3055+#. modules: analytic_override, financing_contract
3056+#: field:account.analytic.account,select_accounts_only:0
3057+#: field:financing.contract.format.line,reporting_select_accounts_only:0
3058+msgid "Select Accounts Only"
3059+msgstr "Sélectionner des Comptes Uniquement"
3060+
3061+#. module: account_override
3062+#: field:account.account,selected_in_fp:0
3063+msgid "Selected in Funding Pool"
3064+msgstr "Sélectionné dans le Funding Pool"
3065+
3066+#. module: account_override
3067+#: field:account.account,selectable_in_contract:0
3068+msgid "Selectable in Contract"
3069+msgstr "Sélectionnable dans le Contrat"
3070+
3071+#. module: account_override
3072+#: field:account.account,selected_in_contract:0
3073+msgid "Selected in Contract"
3074+msgstr "Sélectionné dans le Contrat"
3075+
3076+#. module: financing_contract
3077+#: sql_constraint:financing.contract.account.quadruplet:0
3078+msgid "not unique!"
3079+msgstr "non unique !"
3080+
3081+#. module: financing_contract
3082+#: field:financing.contract.format.line,quadruplet_sync_list:0
3083+msgid "Used to sync quad"
3084+msgstr "Utilisé pour synch. les quadruplets"
3085+
3086+#. module: financing_contract
3087+#: field:financing.contract.contract,quad_gen_date:0
3088+msgid "Date of last generation of quad"
3089+msgstr "Date de la dernière génération des quadruplets"
3090+
3091 #. module: purchase_override
3092 #: view:purchase.order.cancel.wizard:0
3093 msgid "Please confirm that you want to cancel all non-confirmed lines of this PO"
3094@@ -61535,10 +61597,10 @@
3095 msgstr "Le total hors-taxe"
3096
3097 #. module: register_accounting
3098-#: code:addons/register_accounting/wizard/wizard_cash_return.py:800
3099+#: code:addons/register_accounting/wizard/wizard_cash_return.py:824
3100 #, python-format
3101-msgid "All advance lines with account that depends on analytic distribution must have an allocation."
3102-msgstr "All advance lines with account that depends on analytic distribution must have an allocation."
3103+msgid "All advance lines with an account depending on an analytic distribution must have a valid allocation."
3104+msgstr "Toutes les lignes d'avance dont le compte dépend d'une distribution analytique doivent avoir une allocation valide."
3105
3106 #. module: stock
3107 #: field:product.category,property_stock_account_output_categ:0
3108@@ -69077,7 +69139,6 @@
3109 msgstr "Canceled End"
3110
3111 #. modules: financing_contract, analytic_distribution
3112-#: field:financing.contract.account.quadruplet,account_destination_id:0
3113 #: field:account.analytic.account,tuple_destination_account_ids:0
3114 #: view:account.destination.link:0
3115 #: view:account.destination.link:0
3116@@ -94241,6 +94302,7 @@
3117 #: view:account.destination.summary:0
3118 #: code:addons/stock_override/report/report_stock_move.py:759
3119 #: report:addons/account/report/invoice_excel_export.mako:75
3120+#: field:financing.contract.account.quadruplet,account_destination_id:0
3121 #, python-format
3122 msgid "Destination"
3123 msgstr "Destination"
3124@@ -104790,7 +104852,20 @@
3125 msgstr "Filtre non mis en oeuvre sur les Destinations."
3126
3127 #. module: analytic_override
3128+#: code:addons/analytic_override/analytic_account.py:307
3129+#, python-format
3130+msgid "Filter not implemented on Funding Pools."
3131+msgstr "Filtre non mis en oeuvre sur les Funding Pools."
3132+
3133+#. module: analytic_override
3134+#: code:addons/analytic_override/analytic_account.py:312
3135+#, python-format
3136+msgid "Filter only compatible with a normal-type Cost Center."
3137+msgstr "Filtre compatible uniquement avec un Centre de Coût de type normal."
3138+
3139+#. module: analytic_override
3140 #: field:account.analytic.account,allow_all_cc:0
3141+#: field:account.analytic.account,allow_all_cc_with_fp:0
3142 msgid "Allow all Cost Centers"
3143 msgstr "Autoriser tous les Centres de Coût"
3144
3145@@ -104806,17 +104881,39 @@
3146 msgstr "Destinations compatibles avec le Centre de Coût"
3147
3148 #. module: analytic_override
3149+#: field:account.analytic.account,fp_compatible_with_cc_ids:0
3150+msgid "Funding Pools compatible with the Cost Center"
3151+msgstr "Funding Pools compatibles avec le Centre de Coût"
3152+
3153+#. module: analytic_override
3154+#: field:account.analytic.account,fp_compatible_with_acc_dest_ids:0
3155+msgid "Funding Pools compatible with the Account/Destination combination"
3156+msgstr "Funding Pools compatibles avec la combinaison Compte/Destination"
3157+
3158+#. module: analytic_override
3159 #: code:addons/analytic_override/analytic_account.py:347
3160 #, python-format
3161 msgid "Please remove the Cost Centers linked to the Destination before ticking this box."
3162 msgstr "Veuillez supprimer les Centres de Coût liés à la Destination avant de cocher cette case."
3163
3164+#. module: analytic_override
3165+#: code:addons/analytic_override/analytic_account.py:347
3166+#, python-format
3167+msgid "Please remove the Cost Centers linked to the Funding Pool before ticking this box."
3168+msgstr "Veuillez supprimer les Centres de Coût liés au Funding Pool avant de cocher cette case."
3169+
3170 #. module: account_corrections
3171 #: code:addons/account_corrections/wizard/analytic_distribution_wizard.py:246
3172 #, python-format
3173 msgid "The Cost Center %s is not compatible with the Destination %s."
3174 msgstr "Le Centre de Coût %s n'est pas compatible avec la Destination %s."
3175
3176+#. module: account_corrections
3177+#: code:addons/account_corrections/wizard/analytic_distribution_wizard.py:274
3178+#, python-format
3179+msgid "The Cost Center %s is not compatible with the Funding Pool %s."
3180+msgstr "Le Centre de Coût %s n'est pas compatible avec le Funding Pool %s."
3181+
3182 #. module: msf_doc_import
3183 #: code:addons/msf_doc_import/account.py:490
3184 #, python-format
3185@@ -105166,6 +105263,12 @@
3186 msgid "Employee %s: the Cost Center %s is not compatible with the Destination %s."
3187 msgstr "Employé %s : le Centre de Coût %s n'est pas compatible avec la Destination %s."
3188
3189+#. module: msf_homere_interface
3190+#: code:addons/msf_homere_interface/hr.py:229
3191+#, python-format
3192+msgid "Employee %s: the Cost Center %s is not compatible with the Funding Pool %s."
3193+msgstr "Employé %s : le Centre de Coût %s n'est pas compatible avec le Funding Pool %s."
3194+
3195 #. module: msf_supply_doc_export
3196 #: report:po.follow.up_rml:0
3197 msgid "Status:"
3198@@ -109907,6 +110010,11 @@
3199 msgid "Missions where the CC is added to"
3200 msgstr "Missions dans lesquelles le CC est ajouté"
3201
3202+#. module: analytic_override
3203+#: field:account.analytic.account,cc_instance_ids:0
3204+msgid "Instances where the CC is added to"
3205+msgstr "Instances dans lesquelles le CC est ajouté"
3206+
3207 #. module: sync_client
3208 #: field:sync_client.survey.user,nb_displayed:0
3209 msgid "# Display"
3210
3211=== modified file 'bin/addons/msf_profile/msf_profile.py'
3212--- bin/addons/msf_profile/msf_profile.py 2020-10-09 13:44:47 +0000
3213+++ bin/addons/msf_profile/msf_profile.py 2020-11-02 12:53:07 +0000
3214@@ -53,6 +53,25 @@
3215 }
3216
3217 # UF19.0
3218+ def us_7243_migrate_contract_quad(self, cr, uid, *a, **b):
3219+ quad_obj = self.pool.get('financing.contract.account.quadruplet')
3220+ if not cr.table_exists('financing_contract_actual_account_quadruplets_old'):
3221+ cr.execute("create table financing_contract_actual_account_quadruplets_old as (select * from financing_contract_actual_account_quadruplets)")
3222+ already_migrated = {}
3223+ cr.execute('truncate financing_contract_actual_account_quadruplets')
3224+ cr.execute('select actual_line_id, account_quadruplet_id from financing_contract_actual_account_quadruplets_old')
3225+ nb_mig = 0
3226+ for x in cr.fetchall():
3227+ if x[1] not in already_migrated:
3228+ new_id = quad_obj.migrate_old_quad(cr, uid, [x[1]])
3229+ already_migrated[x[1]] = new_id and new_id[0]
3230+ if already_migrated.get(x[1]):
3231+ nb_mig += 1
3232+ cr.execute('insert into financing_contract_actual_account_quadruplets (actual_line_id, account_quadruplet_id) values (%s, %s)', (x[0], already_migrated[x[1]]))
3233+
3234+ self._logger.warn('%d quad migrated' % (nb_mig,))
3235+ return True
3236+
3237 def us_2725_uf_write_date_on_products(self, cr, uid, *a, **b):
3238 '''
3239 Set the uf_write_date of products which don't have one to the date of creation
3240@@ -3910,10 +3929,7 @@
3241 except Exception as e:
3242 err_msg = 'Error with the patch scripts %s.%s :: %s' % (ps['model'], ps['method'], e)
3243 self._logger.error(err_msg)
3244- raise osv.except_osv(
3245- 'Error',
3246- err_msg,
3247- )
3248+ raise
3249
3250 patch_scripts()
3251
3252
3253=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
3254--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-09-01 08:22:26 +0000
3255+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-11-02 12:53:07 +0000
3256@@ -26,7 +26,7 @@
3257 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
3258 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
3259 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
3260-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
3261+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
3262 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
3263 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
3264 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
3265@@ -103,12 +103,12 @@
3266 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
3267 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
3268 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
3269-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
3270+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
3271 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
3272 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
3273 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
3274 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
3275-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
3276+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
3277 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
3278 msf_sync_data_server.country,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['code', 'name']",OC,res.country,,Country,Valid,,500
3279 msf_sync_data_server.state,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['code', 'country_id/id', 'name']",OC,res.country.state,,State,Valid,,501
3280
3281=== modified file 'bin/addons/register_accounting/wizard/wizard_cash_return.py'
3282--- bin/addons/register_accounting/wizard/wizard_cash_return.py 2020-02-04 13:20:04 +0000
3283+++ bin/addons/register_accounting/wizard/wizard_cash_return.py 2020-11-02 12:53:07 +0000
3284@@ -820,7 +820,9 @@
3285 # check if any line with an analytic-a-holic account missing the distribution_id value
3286 for st_line in wizard.advance_line_ids:
3287 if st_line.account_id.is_analytic_addicted and st_line.analytic_distribution_state != 'valid':
3288- raise osv.except_osv(_('Warning'), _('All advance lines with account that depends on analytic distribution must have an allocation.'))
3289+ raise osv.except_osv(_('Warning'),
3290+ _('All advance lines with an account depending on an analytic distribution '
3291+ 'must have a valid allocation.'))
3292
3293 # Do computation of total_amount of advance return lines
3294 self.compute_total_amount(cr, uid, ids, context=context)

Subscribers

People subscribed via source and target branches