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

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

Subscribers

People subscribed via source and target branches