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