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

Proposed by jftempo
Status: Merged
Merged at revision: 6055
Proposed branch: lp:~julie-w/unifield-server/US-7903
Merge into: lp:unifield-server
Diff against target: 1870 lines (+697/-403) (has conflicts)
19 files modified
bin/addons/account_override/account_invoice_sync.py (+44/-33)
bin/addons/account_override/invoice.py (+14/-2)
bin/addons/analytic_distribution/account_commitment.py (+100/-25)
bin/addons/analytic_distribution/account_commitment_view.xml (+45/-9)
bin/addons/analytic_distribution/account_commitment_workflow.xml (+16/-0)
bin/addons/analytic_distribution_supply/invoice.py (+150/-107)
bin/addons/analytic_distribution_supply/stock.py (+16/-32)
bin/addons/base/res/res_log.py (+2/-0)
bin/addons/msf_profile/data/patches.xml (+7/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+94/-15)
bin/addons/msf_profile/msf_profile.py (+13/-0)
bin/addons/purchase/purchase_order.py (+10/-1)
bin/addons/purchase/purchase_order_line.py (+53/-25)
bin/addons/purchase/stock.py (+0/-52)
bin/addons/sale/stock.py (+0/-59)
bin/addons/stock/stock.py (+116/-19)
bin/addons/stock_override/stock.py (+0/-23)
bin/addons/sync_so/purchase.py (+7/-1)
bin/osv/expression.py (+10/-0)
Text conflict in bin/addons/msf_profile/data/patches.xml
Text conflict in bin/addons/msf_profile/i18n/fr_MF.po
Text conflict in bin/addons/msf_profile/msf_profile.py
Text conflict in bin/osv/expression.py
To merge this branch: bzr merge lp:~julie-w/unifield-server/US-7903
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+406907@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/account_override/account_invoice_sync.py'
2--- bin/addons/account_override/account_invoice_sync.py 2021-01-26 11:14:14 +0000
3+++ bin/addons/account_override/account_invoice_sync.py 2021-08-11 08:19:49 +0000
4@@ -100,10 +100,43 @@
5 line_name = inv_line.get('name', '')
6 if not line_name: # required field
7 raise osv.except_osv(_('Error'), _("Impossible to retrieve the line description."))
8+ uom_id = False
9+ uom_data = inv_line.get('uos_id', {})
10+ if uom_data:
11+ uom_name = uom_data.get('name', '')
12+ uom_ids = product_uom_obj.search(cr, uid, [('name', '=', uom_name)], limit=1, context=context)
13+ if not uom_ids:
14+ raise osv.except_osv(_('Error'), _("Unit of Measure %s not found.") % uom_name)
15+ uom_id = uom_ids[0]
16+ quantity = inv_line.get('quantity', 0.0)
17+ inv_line_vals = {
18+ 'invoice_id': inv_id,
19+ 'name': line_name,
20+ 'quantity': quantity,
21+ 'price_unit': inv_line.get('price_unit', 0.0),
22+ 'discount': inv_line.get('discount', 0.0),
23+ 'uos_id': uom_id,
24+ }
25+ line_account_id = False
26+ fo_line_dict = inv_line.get('sale_order_line_id') or {}
27+ if from_supply and inv_linked_po and fo_line_dict.get('sync_local_id'):
28+ # fill in the AD at line level if applicable
29+ # search the matching between PO line and invoice line
30+ po_line_ids = pol_obj.search(cr, uid, [('order_id', '=', inv_linked_po.id), ('sync_linked_sol', '=', inv_line['sale_order_line_id']['sync_local_id'])], context=context)
31+ if po_line_ids:
32+ matching_po_line = pol_obj.browse(cr, uid, po_line_ids[0],
33+ fields_to_fetch=['analytic_distribution_id', 'cv_line_ids'], context=context)
34+ inv_line_vals.update({'order_line_id': matching_po_line.id})
35+ if matching_po_line.cv_line_ids:
36+ inv_line_vals.update({'cv_line_ids': [(6, 0, [cvl.id for cvl in matching_po_line.cv_line_ids])]})
37+ # cv_line_ids only contains one CV line: get its account
38+ line_account_id = matching_po_line.cv_line_ids[0].account_id.id
39+ po_line_distrib = matching_po_line.analytic_distribution_id
40+ self._create_analytic_distrib(cr, uid, inv_line_vals, po_line_distrib, context=context) # update inv_line_vals
41 product_id = False
42 product_data = inv_line.get('product_id', {})
43- line_account_id = False
44- # for the lines related to a product: use the account of the product / else use the one of the source invoice line
45+ # for the lines linked to a CV: the CV line account is used (handled above)
46+ # for the other lines related to a product: use the account of the product / else use the one of the source invoice line
47 if product_data:
48 default_code = product_data.get('default_code', '')
49 product_id = so_po_common_obj.get_product_id(cr, uid, product_data, default_code=default_code, context=context) or False
50@@ -113,10 +146,11 @@
51 context=context)
52 if not product.active:
53 raise osv.except_osv(_('Error'), _("The product %s is inactive.") % product.default_code or '')
54- line_account_id = product.product_tmpl_id.property_account_expense and product.product_tmpl_id.property_account_expense.id
55+ if not line_account_id:
56+ line_account_id = product.product_tmpl_id.property_account_expense and product.product_tmpl_id.property_account_expense.id
57 if not line_account_id:
58 line_account_id = product.categ_id and product.categ_id.property_account_expense_categ and product.categ_id.property_account_expense_categ.id
59- else:
60+ elif not line_account_id:
61 account_code = inv_line.get('account_id', {}).get('code', '')
62 if not account_code:
63 raise osv.except_osv(_('Error'), _("Impossible to retrieve the account code at line level."))
64@@ -131,34 +165,9 @@
65 if inv_posting_date < line_account.activation_date or \
66 (line_account.inactivation_date and inv_posting_date >= line_account.inactivation_date):
67 raise osv.except_osv(_('Error'), _('The account "%s - %s" is inactive.') % (line_account.code, line_account.name))
68- uom_id = False
69- uom_data = inv_line.get('uos_id', {})
70- if uom_data:
71- uom_name = uom_data.get('name', '')
72- uom_ids = product_uom_obj.search(cr, uid, [('name', '=', uom_name)], limit=1, context=context)
73- if not uom_ids:
74- raise osv.except_osv(_('Error'), _("Unit of Measure %s not found.") % uom_name)
75- uom_id = uom_ids[0]
76- quantity = inv_line.get('quantity', 0.0)
77- inv_line_vals = {
78- 'invoice_id': inv_id,
79- 'account_id': line_account_id,
80- 'name': line_name,
81- 'quantity': quantity,
82- 'price_unit': inv_line.get('price_unit', 0.0),
83- 'discount': inv_line.get('discount', 0.0),
84- 'product_id': product_id,
85- 'uos_id': uom_id,
86- }
87- fo_line_dict = inv_line.get('sale_order_line_id') or {}
88- if from_supply and inv_linked_po and fo_line_dict.get('sync_local_id'):
89- # fill in the AD at line level if applicable
90- # search the matching between PO line and invoice line
91- po_line_ids = pol_obj.search(cr, uid, [('order_id', '=', inv_linked_po.id), ('sync_linked_sol', '=', inv_line['sale_order_line_id']['sync_local_id'])], context=context)
92- if po_line_ids:
93- matching_po_line = pol_obj.browse(cr, uid, po_line_ids[0], fields_to_fetch=['analytic_distribution_id'], context=context)
94- po_line_distrib = matching_po_line.analytic_distribution_id
95- self._create_analytic_distrib(cr, uid, inv_line_vals, po_line_distrib, context=context) # update inv_line_vals
96+ inv_line_vals.update({'account_id': line_account_id,
97+ 'product_id': product_id,
98+ })
99 inv_line_obj.create(cr, uid, inv_line_vals, context=context)
100
101 def create_invoice_from_sync(self, cr, uid, source, invoice_data, context=None):
102@@ -270,7 +279,9 @@
103 if po_ids:
104 po_id = po_ids[0]
105 if po_id:
106- vals.update({'main_purchase_id': po_id})
107+ vals.update({'main_purchase_id': po_id,
108+ 'purchase_ids': [(6, 0, [po_id])],
109+ })
110 po_fields = ['picking_ids', 'analytic_distribution_id', 'order_line', 'name']
111 po = po_obj.browse(cr, uid, po_id, fields_to_fetch=po_fields, context=context)
112 po_number = po.name
113
114=== modified file 'bin/addons/account_override/invoice.py'
115--- bin/addons/account_override/invoice.py 2021-04-29 16:00:54 +0000
116+++ bin/addons/account_override/invoice.py 2021-08-11 08:19:49 +0000
117@@ -1423,6 +1423,8 @@
118 vals = by_account_vals[l.account_id.id]
119 if l.order_line_id:
120 vals.setdefault('purchase_order_line_ids', []).append(l.order_line_id.id)
121+ if l.cv_line_ids:
122+ vals.setdefault('cv_line_ids', []).extend([cvl.id for cvl in l.cv_line_ids])
123 else:
124 # new account to merge
125 vals = vals_template.copy()
126@@ -1430,9 +1432,12 @@
127 '_index_': index,
128 'account_id': l.account_id.id,
129 'purchase_order_line_ids': [],
130+ 'cv_line_ids': [],
131 })
132 if l.order_line_id:
133 vals['purchase_order_line_ids'].append(l.order_line_id.id)
134+ if l.cv_line_ids:
135+ vals['cv_line_ids'].extend([cvl.id for cvl in l.cv_line_ids])
136 index += 1
137
138 '''
139@@ -1515,6 +1520,8 @@
140
141 vals['purchase_order_line_ids'] = vals['purchase_order_line_ids'] and [(6, 0, vals['purchase_order_line_ids'])] or False
142
143+ vals['cv_line_ids'] = vals['cv_line_ids'] and [(6, 0, vals['cv_line_ids'])] or False
144+
145 # create merge line
146 vals.update({'merged_line': True})
147 if not self.pool.get('account.invoice.line').create(cr, uid,
148@@ -1797,6 +1804,10 @@
149 ('out_refund', 'Customer Refund'),
150 ('in_refund', 'Supplier Refund')]),
151 'merged_line': fields.boolean(string='Merged Line', help='Line generated by the merging of other lines', readonly=True),
152+ # - a CV line can be linked to several invoice lines ==> e.g. several partial deliveries, split of invoice lines
153+ # - an invoice line can be linked to several CV lines => e.g. merge invoice lines by account
154+ 'cv_line_ids': fields.many2many('account.commitment.line', 'inv_line_cv_line_rel', 'inv_line_id', 'cv_line_id',
155+ string='Commitment Voucher Lines'),
156 }
157
158 _defaults = {
159@@ -1943,7 +1954,7 @@
160 """
161 Copy an invoice line without its move lines,
162 without the link to a reversed invoice line,
163- and without link to PO/FO lines when the duplication is manual
164+ and without link to PO/FO/CV lines when the duplication is manual
165 Reset the merged_line tag.
166 """
167 if context is None:
168@@ -1955,13 +1966,14 @@
169 'merged_line': False,
170 })
171 # Manual duplication should generate a "manual document not created through the supply workflow"
172- # so we don't keep the link to PO/FO at line level
173+ # so we don't keep the link to PO/FO/CV at line level
174 if context.get('from_button') and not context.get('from_split'):
175 default.update({
176 'order_line_id': False,
177 'sale_order_line_id': False,
178 'sale_order_lines': False,
179 'purchase_order_line_ids': [],
180+ 'cv_line_ids': [(6, 0, [])],
181 })
182 return super(account_invoice_line, self).copy_data(cr, uid, inv_id, default, context)
183
184
185=== modified file 'bin/addons/analytic_distribution/account_commitment.py'
186--- bin/addons/analytic_distribution/account_commitment.py 2021-05-11 18:16:37 +0000
187+++ bin/addons/analytic_distribution/account_commitment.py 2021-08-11 08:19:49 +0000
188@@ -67,6 +67,38 @@
189 res.append(cvl.commit_id.id)
190 return res
191
192+ def get_cv_type(self, cr, uid, context=None):
193+ """
194+ Returns the list of possible types for the Commitment Vouchers
195+ """
196+ return [('manual', 'Manual'),
197+ ('external', 'Automatic - External supplier'),
198+ ('esc', 'Manual - ESC supplier'),
199+ ('intermission', 'Automatic - Intermission'),
200+ ('intersection', 'Automatic - Intersection'),
201+ ]
202+
203+ def get_current_cv_version(self, cr, uid, context=None):
204+ """
205+ Version 2 since US-7449
206+ """
207+ return 2
208+
209+ def _display_super_done_button(self, cr, uid, ids, name, arg, context=None):
210+ """
211+ For now the "Super" Done button, which allows to always set a CV to Done whatever its state and origin,
212+ is visible only by the Admin user. It is displayed only when the standard Done button isn't usable.
213+ """
214+ if context is None:
215+ context = {}
216+ if isinstance(ids, (int, long)):
217+ ids = [ids]
218+ res = {}
219+ for cv in self.read(cr, uid, ids, ['state', 'type'], context=context):
220+ other_done_button_usable = cv['state'] == 'open' and cv['type'] not in ('external', 'intermission', 'intersection')
221+ res[cv['id']] = not other_done_button_usable and uid == 1 and cv['state'] != 'done'
222+ return res
223+
224 _columns = {
225 'journal_id': fields.many2one('account.analytic.journal', string="Journal", readonly=True, required=True),
226 'name': fields.char(string="Number", size=64, readonly=True, required=True),
227@@ -81,16 +113,22 @@
228 'account.commitment.line': (_get_cv, ['amount'],10),
229 }),
230 'analytic_distribution_id': fields.many2one('analytic.distribution', string="Analytic distribution"),
231- 'type': fields.selection([('manual', 'Manual'), ('external', 'Automatic - External supplier'), ('esc', 'Manual - ESC supplier')], string="Type", readonly=True),
232+ 'type': fields.selection(get_cv_type, string="Type", readonly=True),
233 'notes': fields.text(string="Comment"),
234 'purchase_id': fields.many2one('purchase.order', string="Source document", readonly=True),
235 'description': fields.char(string="Description", size=256),
236+ 'version': fields.integer('Version', required=True,
237+ help="Technical field to distinguish old CVs from new ones which have a different behavior."),
238+ 'display_super_done_button': fields.function(_display_super_done_button, method=True, type='boolean',
239+ store=False, invisible=True,
240+ string='Display the button allowing to always set a CV to Done'),
241 }
242
243 _defaults = {
244 'state': lambda *a: 'draft',
245 'date': lambda *a: strftime('%Y-%m-%d'),
246 'type': lambda *a: 'manual',
247+ 'version': get_current_cv_version,
248 'journal_id': lambda s, cr, uid, c: s.pool.get('account.analytic.journal').search(cr, uid, [('type', '=', 'engagement'),
249 ('instance_id', '=', s.pool.get('res.users').browse(cr, uid, uid, c).company_id.instance_id.id)], limit=1, context=c)[0]
250 }
251@@ -181,29 +219,30 @@
252 fctal_currency = user_obj.browse(cr, uid, uid, fields_to_fetch=['company_id'], context=context).company_id.currency_id.id
253 for cl in c.line_ids:
254 # Verify that date is compatible with all analytic account from distribution
255+ distrib = False
256 if cl.analytic_distribution_id:
257 distrib = cl.analytic_distribution_id
258 elif cl.commit_id and cl.commit_id.analytic_distribution_id:
259 distrib = cl.commit_id.analytic_distribution_id
260- else:
261- raise osv.except_osv(_('Warning'), _('No analytic distribution found for %s %s') % (cl.account_id.code, cl.initial_amount))
262- for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
263- for distrib_line in distrib_lines:
264- if distrib_line.analytic_id and \
265- (distrib_line.analytic_id.date_start and date < distrib_line.analytic_id.date_start or
266- distrib_line.analytic_id.date and date >= distrib_line.analytic_id.date):
267- raise osv.except_osv(_('Error'), _('The analytic account %s is not active for given date.') %
268- (distrib_line.analytic_id.name,))
269- dest_cc_tuples = set() # check each Dest/CC combination only once
270- for distrib_cc_l in distrib.cost_center_lines:
271- if distrib_cc_l.analytic_id: # non mandatory field
272- dest_cc_tuples.add((distrib_cc_l.destination_id, distrib_cc_l.analytic_id))
273- for distrib_fp_l in distrib.funding_pool_lines:
274- dest_cc_tuples.add((distrib_fp_l.destination_id, distrib_fp_l.cost_center_id))
275- for dest, cc in dest_cc_tuples:
276- if dest_cc_link_obj.is_inactive_dcl(cr, uid, dest.id, cc.id, date, context=context):
277- raise osv.except_osv(_('Error'), _("The combination \"%s - %s\" is not active at this date: %s") %
278- (dest.code or '', cc.code or '', date))
279+ if distrib:
280+ for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines,
281+ distrib.free_1_lines, distrib.free_2_lines]:
282+ for distrib_line in distrib_lines:
283+ if distrib_line.analytic_id and \
284+ (distrib_line.analytic_id.date_start and date < distrib_line.analytic_id.date_start or
285+ distrib_line.analytic_id.date and date >= distrib_line.analytic_id.date):
286+ raise osv.except_osv(_('Error'), _('The analytic account %s is not active for given date.') %
287+ (distrib_line.analytic_id.name,))
288+ dest_cc_tuples = set() # check each Dest/CC combination only once
289+ for distrib_cc_l in distrib.cost_center_lines:
290+ if distrib_cc_l.analytic_id: # non mandatory field
291+ dest_cc_tuples.add((distrib_cc_l.destination_id, distrib_cc_l.analytic_id))
292+ for distrib_fp_l in distrib.funding_pool_lines:
293+ dest_cc_tuples.add((distrib_fp_l.destination_id, distrib_fp_l.cost_center_id))
294+ for dest, cc in dest_cc_tuples:
295+ if dest_cc_link_obj.is_inactive_dcl(cr, uid, dest.id, cc.id, date, context=context):
296+ raise osv.except_osv(_('Error'), _("The combination \"%s - %s\" is not active at this date: %s") %
297+ (dest.code or '', cc.code or '', date))
298 # update the dates and fctal amounts of the related analytic lines
299 context.update({'currency_date': date}) # same date used for doc, posting and source date of all lines
300 for aal in cl.analytic_lines:
301@@ -233,6 +272,7 @@
302 default.update({
303 'name': self.pool.get('ir.sequence').get(cr, uid, 'account.commitment'),
304 'state': 'draft',
305+ 'version': self.get_current_cv_version(cr, uid, context=context),
306 })
307 # Default method
308 res = super(account_commitment, self).copy(cr, uid, c_id, default, context)
309@@ -312,6 +352,12 @@
310 'context': context,
311 }
312
313+ def button_analytic_distribution_2(self, cr, uid, ids, context=None):
314+ """
315+ This is just an alias for button_analytic_distribution (used to have different names and attrs on both buttons)
316+ """
317+ return self.button_analytic_distribution(cr, uid, ids, context=context)
318+
319 def button_reset_distribution(self, cr, uid, ids, context=None):
320 """
321 Reset analytic distribution on all commitment lines.
322@@ -444,10 +490,11 @@
323 # Search analytic lines that have commitment line ids
324 search_ids = self.pool.get('account.analytic.line').search(cr, uid, [('commitment_line_id', 'in', [x.id for x in c.line_ids])], context=context)
325 # Delete them
326- res = self.pool.get('account.analytic.line').unlink(cr, uid, search_ids, context=context)
327+ if search_ids:
328+ res = self.pool.get('account.analytic.line').unlink(cr, uid, search_ids, context=context)
329+ if not res:
330+ raise osv.except_osv(_('Error'), _('An error occurred on engagement lines deletion.'))
331 # And finally update commitment voucher state and lines amount
332- if not res:
333- raise osv.except_osv(_('Error'), _('An error occurred on engagement lines deletion.'))
334 self.pool.get('account.commitment.line').write(cr, uid, [x.id for x in c.line_ids], {'amount': 0}, context=context)
335 self.write(cr, uid, [c.id], {'state':'done'}, context=context)
336 return True
337@@ -457,7 +504,7 @@
338 class account_commitment_line(osv.osv):
339 _name = 'account.commitment.line'
340 _description = "Account Commitment Voucher Line"
341- _order = "id desc"
342+ _order = "po_line_id, id desc"
343 _rec_name = 'account_id'
344 _trace = True
345
346@@ -497,6 +544,12 @@
347 res[co.id] = False
348 return res
349
350+ def get_cv_type(self, cr, uid, context=None):
351+ """
352+ Gets the possible CV types
353+ """
354+ return self.pool.get('account.commitment').get_cv_type(cr, uid, context)
355+
356 _columns = {
357 'account_id': fields.many2one('account.account', string="Account", required=True),
358 'amount': fields.float(string="Amount left", digits_compute=dp.get_precision('Account'), required=False),
359@@ -504,6 +557,8 @@
360 'commit_id': fields.many2one('account.commitment', string="Commitment Voucher", on_delete="cascade"),
361 'commit_number': fields.related('commit_id', 'name', type='char', size=64,
362 readonly=True, store=False, string="Commitment Voucher Number"),
363+ 'commit_type': fields.related('commit_id', 'type', string="Commitment Voucher Type", type='selection', readonly=True,
364+ store=False, invisible=True, selection=get_cv_type, write_relate=False),
365 'analytic_distribution_id': fields.many2one('analytic.distribution', string="Analytic distribution"),
366 'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
367 selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
368@@ -513,8 +568,15 @@
369 'analytic_lines': fields.one2many('account.analytic.line', 'commitment_line_id', string="Analytic Lines"),
370 'first': fields.boolean(string="Is not created?", help="Useful for onchange method for views. Should be False after line creation.",
371 readonly=True),
372+ # for CV in version 1
373 'purchase_order_line_ids': fields.many2many('purchase.order.line', 'purchase_line_commitment_rel', 'commitment_id', 'purchase_id',
374- string="Purchase Order Lines", readonly=True),
375+ string="Purchase Order Lines (deprecated)", readonly=True),
376+ # for CV starting from version 2
377+ 'po_line_id': fields.many2one('purchase.order.line', "PO Line"),
378+ 'po_line_product_id': fields.related('po_line_id', 'product_id', type='many2one', relation='product.product',
379+ string="Product", readonly=True, store=True, write_relate=False),
380+ 'po_line_number': fields.related('po_line_id', 'line_number', type='integer_null', string="PO Line", readonly=True,
381+ store=True, write_relate=False, _fnct_migrate=lambda *a: True),
382 }
383
384 _defaults = {
385@@ -656,6 +718,19 @@
386 self.update_analytic_lines(cr, uid, [line.id], vals.get('amount'), account_id, context=context)
387 return super(account_commitment_line, self).write(cr, uid, ids, vals, context={})
388
389+ def copy_data(self, cr, uid, cv_line_id, default=None, context=None):
390+ """
391+ Duplicates a CV line: resets the link to PO line
392+ """
393+ if context is None:
394+ context = {}
395+ if default is None:
396+ default = {}
397+ default.update({
398+ 'po_line_id': False,
399+ })
400+ return super(account_commitment_line, self).copy_data(cr, uid, cv_line_id, default, context)
401+
402 def button_analytic_distribution(self, cr, uid, ids, context=None):
403 """
404 Launch analytic distribution wizard on a commitment voucher line
405
406=== modified file 'bin/addons/analytic_distribution/account_commitment_view.xml'
407--- bin/addons/analytic_distribution/account_commitment_view.xml 2020-09-23 09:29:11 +0000
408+++ bin/addons/analytic_distribution/account_commitment_view.xml 2021-08-11 08:19:49 +0000
409@@ -28,20 +28,36 @@
410 <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft"/>
411 </group>
412 <group colspan="8" col="8" attrs="{'invisible': [('analytic_distribution_id', '!=', False)]}">
413- <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-emblem-important" context="context" colspan="4" attrs="{'invisible': [('analytic_distribution_id', '!=', False)]}"/>
414+ <button name="button_analytic_distribution_2" string="Analytical Distribution"
415+ type="object" icon="terp-emblem-important" context="context" colspan="4"
416+ attrs="{'readonly': [('state', '=', 'open'), ('type', '=', 'manual')]}"/>
417 <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft"/>
418 </group>
419 <field name="analytic_distribution_id" invisible="1"/>
420 <notebook colspan="4">
421 <page string="Commitment voucher lines">
422- <field name="line_ids" nolabel="1" colspan="4" attrs="{'readonly': ['|', ('state', '=', 'done'), ('type', '=', 'external')]}"/>
423+ <field name="line_ids" nolabel="1" colspan="4"
424+ attrs="{'readonly': ['|', ('state', '=', 'done'),
425+ '&amp;',
426+ ('type', 'in', ['external', 'intermission', 'intersection']),
427+ ('state', '!=', 'draft')]}"
428+ />
429 </page>
430 </notebook>
431 <field name="notes" colspan="4"/>
432 <group col="6" colspan="4">
433 <button name="button_compute" string="Compute total" icon="gtk-execute" colspan="2"/>
434 <button name="commitment_open" string="Validate" icon="terp-camera_test" states="draft" colspan="2"/>
435- <button name="commitment_validate" string="Done" icon="terp-gtk-go-back-rtl" states="open" colspan="2" attrs="{'readonly': [('type', '=', 'external')]}"/>
436+ <field name="display_super_done_button"/>
437+ <button name="commitment_validate" string="Done" icon="terp-gtk-go-back-rtl" colspan="2"
438+ attrs="{'invisible': ['|', ('state', '!=', 'open'), ('display_super_done_button', '=', True)],
439+ 'readonly': [('type', 'in', ['external', 'intermission', 'intersection'])]}"
440+ />
441+ <!-- button which allows to always set a CV to Done (whatever its state and origin) -->
442+ <button name="commitment_always_validate" string="Done (for Administrator only)"
443+ icon="terp-gtk-go-back-rtl" colspan="6"
444+ confirm='You are about to set this Commitment Voucher to the state "Done". Do you want to proceed?'
445+ attrs="{'invisible': [('display_super_done_button', '=', False)]}"/>
446 </group>
447 <field name="state"/>
448 <field name="total"/>
449@@ -73,14 +89,32 @@
450 <field name="model">account.commitment.line</field>
451 <field name="type">tree</field>
452 <field name="arch" type="xml">
453- <tree string="Commitment Voucher Lines" editable="top" colors="red:analytic_distribution_state == 'invalid'">
454+ <!-- display the "New" button or not depending on the state and type of the CV itself -->
455+ <tree string="Commitment Voucher Lines" editable="top" colors="red:analytic_distribution_state == 'invalid'"
456+ button_attrs="{'invisible': ['|', ('state', '=', 'done'), ('type', 'in', ['external', 'intermission', 'intersection'])]}"
457+ hide_delete_button="1"
458+ >
459+ <field name="commit_type"/>
460+ <field name="po_line_product_id"/>
461+ <field name="po_line_number"/>
462 <field name="account_id" domain="[('restricted_area', '=', 'commitment_lines')]"/>
463- <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-stock_symbol-selection" context="context"/>
464- <field name="analytic_distribution_state"/>
465- <field name="have_analytic_distribution_from_header"/>
466+ <button name="button_analytic_distribution" string="Analytical Distribution" type="object"
467+ icon="terp-stock_symbol-selection" context="context"
468+ />
469+ <field name="analytic_distribution_state" readonly="1"/>
470+ <field name="have_analytic_distribution_from_header" readonly="1"/>
471 <field name="first" invisible="1"/>
472- <field name="initial_amount" on_change="onchange_initial_amount(first, initial_amount)"/>
473- <field name="amount" attrs="{'readonly': [('first', '=', True)]}"/>
474+ <field name="initial_amount" on_change="onchange_initial_amount(first, initial_amount)"
475+ attrs="{'readonly': [('commit_type', 'in', ['external', 'intermission', 'intersection'])]}"
476+ />
477+ <field name="amount"
478+ attrs="{'readonly': ['|', ('first', '=', True), ('commit_type', 'in', ['external', 'intermission', 'intersection'])]}"
479+ />
480+ <!-- display the "Delete" button or not depending on the state and type of the CV itself -->
481+ <button name="unlink" string="Delete" icon="gtk-del" type="object"
482+ attrs="{'invisible': ['|', ('state', '=', 'done'), ('type', 'in', ['external', 'intermission', 'intersection'])]}"
483+ confirm="Do you really want to delete this line?"
484+ />
485 </tree>
486 </field>
487 </record>
488@@ -95,6 +129,8 @@
489 <group col='6' colspan='4'>
490 <filter icon="terp-tools" string="Manual" domain="[('type','=','manual')]" help="Manual Commitment Voucher"/>
491 <filter icon="gtk-quit" string="External" domain="[('type','=','external')]" help="External Commitment Voucher"/>
492+ <filter icon="gtk-refresh" string="Intersection" domain="[('type', '=', 'intersection')]" help="Intersection Commitment Voucher"/>
493+ <filter icon="gtk-ok" string="Intermission" domain="[('type', '=', 'intermission')]" help="Intermission Commitment Voucher"/>
494 <filter icon="terp-partner" string="ESC" domain="[('type','=','esc')]" help="ESC Commitment Voucher"/>
495 <separator orientation="vertical"/>
496 <filter icon="terp-document-new" string="Draft" domain="[('state','=','draft')]" help="Commitment Voucher in Draft state" name="draft"/>
497
498=== modified file 'bin/addons/analytic_distribution/account_commitment_workflow.xml'
499--- bin/addons/analytic_distribution/account_commitment_workflow.xml 2011-11-30 14:56:38 +0000
500+++ bin/addons/analytic_distribution/account_commitment_workflow.xml 2021-08-11 08:19:49 +0000
501@@ -34,14 +34,30 @@
502 <record id="commit_t1" model="workflow.transition">
503 <field name="act_from" ref="act_draft"/>
504 <field name="act_to" ref="act_open"/>
505+ <field name="sequence">10</field>
506 <field name="signal">commitment_open</field>
507 </record>
508
509 <record id="commit_t2" model="workflow.transition">
510 <field name="act_from" ref="act_open"/>
511 <field name="act_to" ref="act_done"/>
512+ <field name="sequence">10</field>
513 <field name="signal">commitment_validate</field>
514 </record>
515
516+ <record id="commit_t3" model="workflow.transition">
517+ <field name="act_from" ref="act_open"/>
518+ <field name="act_to" ref="act_done"/>
519+ <field name="sequence">15</field> <!-- (sequence, act_from) must be unique -->
520+ <field name="signal">commitment_always_validate</field>
521+ </record>
522+
523+ <record id="commit_t4" model="workflow.transition">
524+ <field name="act_from" ref="act_draft"/>
525+ <field name="act_to" ref="act_done"/>
526+ <field name="sequence">15</field> <!-- (sequence, act_from) must be unique -->
527+ <field name="signal">commitment_always_validate</field>
528+ </record>
529+
530 </data>
531 </openerp>
532
533=== modified file 'bin/addons/analytic_distribution_supply/invoice.py'
534--- bin/addons/analytic_distribution_supply/invoice.py 2021-01-26 13:48:04 +0000
535+++ bin/addons/analytic_distribution_supply/invoice.py 2021-08-11 08:19:49 +0000
536@@ -26,6 +26,7 @@
537 from osv import fields
538 from tools.translate import _
539 from base import currency_date
540+import netsvc
541
542
543 class account_invoice_line(osv.osv):
544@@ -93,20 +94,27 @@
545 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'analytic_distribution_id': new_distrib_id,})
546 # Then set distribution on invoice line regarding purchase order line distribution
547 for invl in inv.invoice_line:
548- if invl.order_line_id:
549+ line_distrib_id = False
550+ if invl.cv_line_ids:
551+ # CV STARTING FROM VERSION 2
552+ # the first CV line found is used since there can be only one at this step (merging lines by account could
553+ # generate an invoice line linked to several CV lines but this action can only be done later in the process)
554+ line_distrib_id = invl.cv_line_ids[0].analytic_distribution_id and invl.cv_line_ids[0].analytic_distribution_id.id or False
555+ elif invl.order_line_id:
556 # Fetch PO line analytic distribution or nothing (that implies it take those from PO)
557- distrib_id = invl.order_line_id.analytic_distribution_id and invl.order_line_id.analytic_distribution_id.id or False
558+ line_distrib_id = invl.order_line_id.analytic_distribution_id and invl.order_line_id.analytic_distribution_id.id or False
559 # Attempt to fetch commitment line analytic distribution or commitment voucher analytic distribution or default distrib_id
560+ # CV IN VERSION 1
561 if invl.order_line_id.commitment_line_ids:
562- distrib_id = invl.order_line_id.commitment_line_ids[0].analytic_distribution_id \
563- and invl.order_line_id.commitment_line_ids[0].analytic_distribution_id.id or distrib_id
564- if distrib_id:
565- new_invl_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
566- if not new_invl_distrib_id:
567- raise osv.except_osv(_('Error'), _('An error occurred for analytic distribution copy for invoice.'))
568- # create default funding pool lines
569- ana_obj.create_funding_pool_lines(cr, uid, [new_invl_distrib_id], invl.account_id.id)
570- invl_obj.write(cr, uid, [invl.id], {'analytic_distribution_id': new_invl_distrib_id})
571+ line_distrib_id = invl.order_line_id.commitment_line_ids[0].analytic_distribution_id \
572+ and invl.order_line_id.commitment_line_ids[0].analytic_distribution_id.id or line_distrib_id
573+ if line_distrib_id:
574+ new_invl_distrib_id = ana_obj.copy(cr, uid, line_distrib_id, {})
575+ if not new_invl_distrib_id:
576+ raise osv.except_osv(_('Error'), _('An error occurred for analytic distribution copy for invoice.'))
577+ # create default funding pool lines
578+ ana_obj.create_funding_pool_lines(cr, uid, [new_invl_distrib_id], invl.account_id.id)
579+ invl_obj.write(cr, uid, [invl.id], {'analytic_distribution_id': new_invl_distrib_id})
580 # Fetch SO line analytic distribution
581 # sol AD copy moved into _invoice_line_hook
582 return True
583@@ -124,7 +132,8 @@
584
585 # Browse invoices
586 for inv in self.browse(cr, uid, ids, context=context):
587- grouped_invl = {}
588+ grouped_invl_by_acc = {}
589+ grouped_invl_by_cvl = {}
590 co_ids = self.pool.get('account.commitment').search(cr, uid, [('purchase_id', 'in', [x.id for x in inv.purchase_ids]), ('state', 'in', ['open', 'draft'])], order='date desc', context=context)
591 if not co_ids:
592 continue
593@@ -133,27 +142,50 @@
594 # Do not take invoice line that have no order_line_id (so that are not linked to a purchase order line)
595 if not invl.order_line_id and not inv.is_merged_by_account:
596 continue
597-
598- # Fetch purchase order line account
599- if inv.is_merged_by_account:
600- if not invl.account_id:
601- continue
602- # US-357: lines without product (get directly account)
603- a = invl.account_id.id
604- else:
605- pol = invl.order_line_id
606- a = self._get_expense_account(cr, uid, pol, context=context)
607- if pol.product_id and not a:
608- raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (pol.product_id.name, pol.product_id.id))
609- elif not a:
610- raise osv.except_osv(_('Error !'), _('There is no expense account defined for this PO line: "%s" (id:%d)') % (pol.line_number, pol.id))
611- if a not in grouped_invl:
612- grouped_invl[a] = 0
613-
614- grouped_invl[a] += invl.price_subtotal
615+ # exclude push flow
616+ if invl.order_line_id and (invl.order_line_id.order_id.push_fo or invl.order_line_id.set_as_sourced_n):
617+ continue
618+ old_cv_version = True
619+ # CV STARTING FROM VERSION 2
620+ amount_to_subtract = invl.price_subtotal or 0.0
621+ for cv_line in invl.cv_line_ids:
622+ old_cv_version = False # the field cv_line_ids exist for CVs starting from version 2
623+ if abs(amount_to_subtract) <= 10**-3:
624+ break
625+ cvl_amount_left = cv_line.amount or 0.0
626+ if cvl_amount_left:
627+ if cv_line.id not in grouped_invl_by_cvl:
628+ grouped_invl_by_cvl[cv_line.id] = 0
629+ if amount_to_subtract >= cvl_amount_left:
630+ grouped_invl_by_cvl[cv_line.id] += cvl_amount_left
631+ amount_to_subtract -= cvl_amount_left
632+ else:
633+ grouped_invl_by_cvl[cv_line.id] += amount_to_subtract
634+ amount_to_subtract = 0
635+ # CV IN VERSION 1
636+ if old_cv_version:
637+ # Fetch purchase order line account
638+ if inv.is_merged_by_account:
639+ if not invl.account_id:
640+ continue
641+ # US-357: lines without product (get directly account)
642+ a = invl.account_id.id
643+ else:
644+ pol = invl.order_line_id
645+ a = self._get_expense_account(cr, uid, pol, context=context)
646+ if pol.product_id and not a:
647+ raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') %
648+ (pol.product_id.name, pol.product_id.id))
649+ elif not a:
650+ raise osv.except_osv(_('Error !'), _('There is no expense account defined for this PO line: "%s" (id:%d)') %
651+ (pol.line_number, pol.id))
652+ if a not in grouped_invl_by_acc:
653+ grouped_invl_by_acc[a] = 0
654+ grouped_invl_by_acc[a] += invl.price_subtotal
655
656 po_ids = [x.id for x in inv.purchase_ids]
657- self._update_commitments_lines(cr, uid, po_ids, grouped_invl, from_cancel=False, context=context)
658+ self._update_commitments_lines(cr, uid, po_ids, account_amount_dic=grouped_invl_by_acc,
659+ cvl_amount_dic=grouped_invl_by_cvl, from_cancel=False, context=context)
660
661 return True
662
663@@ -170,38 +202,51 @@
664
665 return account_id
666
667- def _update_commitments_lines(self, cr, uid, po_ids, account_amount_dic, from_cancel=False, context=None):
668+ def _update_commitments_lines(self, cr, uid, po_ids, account_amount_dic=None, cvl_amount_dic=None, from_cancel=False, context=None):
669 """
670 po_ids: list of PO ids
671 account_amount_dic: dict, keys are G/L account_id, values are amount to deduce
672
673
674 """
675- if not po_ids or not account_amount_dic:
676+ if not po_ids or not account_amount_dic and not cvl_amount_dic:
677 return True
678
679 if context is None:
680 context = {}
681+ if account_amount_dic is None:
682+ account_amount_dic = {}
683+ if cvl_amount_dic is None:
684+ cvl_amount_dic = {}
685+ wf_service = netsvc.LocalService("workflow")
686
687 # po is state=cancel on last IN cancel
688 company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
689- cr.execute('''select l.id, l.account_id, l.commit_id, c.state, l.amount, l.analytic_distribution_id, c.analytic_distribution_id, c.id, c.currency_id, c.type from
690+ # avoids empty lists so that the SQL request can be executed
691+ account_list = account_amount_dic.keys() or [0]
692+ cvl_list = cvl_amount_dic.keys() or [0]
693+ cr.execute('''select l.id, l.account_id, l.commit_id, c.state, l.amount, l.analytic_distribution_id, c.analytic_distribution_id,
694+ c.id, c.currency_id, c.type, c.version from
695 account_commitment_line l, account_commitment c
696 where l.commit_id = c.id and
697 l.amount > 0 and
698 c.purchase_id in %s and
699- l.account_id in %s and
700+ (c.version < 2 and l.account_id in %s or c.version >= 2 and l.id in %s) and
701 c.state in ('open', 'draft')
702 order by c.date asc
703- ''', (tuple(po_ids), tuple(account_amount_dic.keys()))
704+ ''', (tuple(po_ids), tuple(account_list), tuple(cvl_list))
705 )
706 # sort all cv lines by account / cv date
707- cv_info = {}
708+ account_info = {}
709+ cvl_info = {}
710 auto_cv = True
711 for cv in cr.fetchall():
712- if cv[1] not in cv_info:
713- cv_info[cv[1]] = []
714- cv_info[cv[1]].append(cv)
715+ if cv[10] < 2:
716+ # CV IN VERSION 1
717+ account_info.setdefault(cv[1], []).append(cv) # key = account id
718+ else:
719+ # CV STARTING FROM VERSION 2
720+ cvl_info.setdefault(cv[0], []).append(cv) # key = CV line id
721 if cv[9] == 'manual':
722 auto_cv = False
723
724@@ -210,76 +255,74 @@
725 cv_to_close = {}
726
727 # deduce amount on oldest cv lines
728- for account in account_amount_dic.keys():
729- if account not in cv_info:
730- continue
731- for cv_line in cv_info[account]:
732- if cv_line[3] == 'draft' and cv_line[2] not in draft_opened and not from_cancel:
733- draft_opened.append(cv_line[2])
734- # If Commitment voucher in draft state we change it to 'validated' without using workflow and engagement lines generation
735- # NB: This permits to avoid modification on commitment voucher when receiving some goods
736- self.pool.get('account.commitment').write(cr, uid, [cv_line[2]], {'state': 'open'}, context=context)
737-
738- if cv_line[4] - account_amount_dic[account] > 0.001:
739- # update amount left on CV line
740- amount_left = cv_line[4] - account_amount_dic[account]
741- self.pool.get('account.commitment.line').write(cr, uid, [cv_line[0]], {'amount': amount_left}, context=context)
742-
743- # update AAL
744- distrib_id = cv_line[5] or cv_line[6]
745- if not distrib_id:
746- raise osv.except_osv(_('Error'), _('No analytic distribution found.'))
747-
748- # Browse distribution
749- distrib = self.pool.get('analytic.distribution').browse(cr, uid, [distrib_id], context=context)[0]
750- engagement_lines = distrib.analytic_lines
751- for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
752- for distrib_line in distrib_lines:
753- vals = {
754- 'account_id': distrib_line.analytic_id.id,
755- 'general_account_id': account,
756- }
757- if distrib_line._name == 'funding.pool.distribution.line':
758- vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,})
759+ # NOTE: account_amount_dic is for CV in version 1 based on accounts (acc),
760+ # cvl_amount_dic is for CV from version 2 based on CV lines (cvl)
761+ for c_type in ("acc", "cvl"):
762+ if c_type == "acc":
763+ cv_info = account_info.copy()
764+ amount_dic = account_amount_dic.copy()
765+ else:
766+ cv_info = cvl_info.copy()
767+ amount_dic = cvl_amount_dic.copy()
768+ for k in amount_dic.keys(): # account id or CV line id
769+ if k not in cv_info:
770+ continue
771+ for cv_line in cv_info[k]:
772+ if cv_line[3] == 'draft' and cv_line[2] not in draft_opened and not from_cancel:
773+ draft_opened.append(cv_line[2])
774+ # Change Draft CV to Validated State, in order to avoid CV modification when receiving some goods.
775+ # The workflow is used so that all the engagement lines are generated, even those which are not
776+ # affected by the current update (e.g. partial reception + SI validation on one in 2 products).
777+ wf_service.trg_validate(uid, 'account.commitment', cv_line[2], 'commitment_open', cr)
778+
779+ if cv_line[4] - amount_dic[k] > 0.001:
780+ # update amount left on CV line
781+ amount_left = cv_line[4] - amount_dic[k]
782+ self.pool.get('account.commitment.line').write(cr, uid, [cv_line[0]], {'amount': amount_left}, context=context)
783+ # update AAL
784+ distrib_id = cv_line[5] or cv_line[6]
785+ if not distrib_id:
786+ raise osv.except_osv(_('Error'), _('No analytic distribution found.'))
787+ # Browse distribution
788+ distrib = self.pool.get('analytic.distribution').browse(cr, uid, [distrib_id], context=context)[0]
789+ engagement_lines = distrib.analytic_lines
790+ for distrib_line in distrib.funding_pool_lines:
791 # Browse engagement lines to found out matching elements
792- for i in range(0,len(engagement_lines)):
793+ for i in range(0, len(engagement_lines)):
794 if engagement_lines[i]:
795 eng_line = engagement_lines[i]
796- cmp_vals = {
797- 'account_id': eng_line.account_id.id,
798- 'general_account_id': eng_line.general_account_id.id,
799- }
800- if eng_line.cost_center_id:
801- cmp_vals.update({'cost_center_id': eng_line.cost_center_id.id})
802- if cmp_vals == vals:
803- # Update analytic line with new amount
804- anal_amount = (distrib_line.percentage * amount_left) / 100
805- curr_date = currency_date.get_date(self, cr, eng_line.document_date, eng_line.date,
806- source_date=eng_line.source_date)
807- context.update({'currency_date': curr_date})
808- amount = -1 * self.pool.get('res.currency').compute(cr, uid, cv_line[8], company_currency,
809- anal_amount, round=False, context=context)
810-
811- # write new amount to corresponding engagement line
812- self.pool.get('account.analytic.line').write(cr, uid, [eng_line.id],
813- {'amount': amount, 'amount_currency': -1 * anal_amount}, context=context)
814-
815- # check next G/L account
816- break
817-
818- cv_to_close[cv_line[2]] = True
819- eng_ids = self.pool.get('account.analytic.line').search(cr, uid, [('commitment_line_id', '=', cv_line[0])], context=context)
820- if eng_ids:
821- self.pool.get('account.analytic.line').unlink(cr, uid, eng_ids, context=context)
822- self.pool.get('account.commitment.line').write(cr, uid, [cv_line[0]], {'amount': 0.0}, context=context)
823- if abs(cv_line[4] - account_amount_dic[account]) < 0.001:
824- # check next G/L account
825- break
826-
827- # check next CV on this account
828- account_amount_dic[account] -= cv_line[4]
829-
830- if auto_cv and from_cancel:
831+ # restrict to the current CV line only
832+ if eng_line.commitment_line_id and eng_line.commitment_line_id.id == cv_line[0]:
833+ eng_line_distrib_id = eng_line.distrib_line_id and \
834+ eng_line.distrib_line_id._name == 'funding.pool.distribution.line' and \
835+ eng_line.distrib_line_id.id or False
836+ # in case of an AD with several lines, several AJIs are linked to the same CV line:
837+ # the comparison is used to decrement the right one
838+ if eng_line_distrib_id == distrib_line.id:
839+ # Update analytic line with new amount
840+ anal_amount = (distrib_line.percentage * amount_left) / 100
841+ curr_date = currency_date.get_date(self, cr, eng_line.document_date, eng_line.date,
842+ source_date=eng_line.source_date)
843+ context.update({'currency_date': curr_date})
844+ amount = -1 * self.pool.get('res.currency').compute(cr, uid, cv_line[8], company_currency,
845+ anal_amount, round=False, context=context)
846+ # write new amount to corresponding engagement line
847+ self.pool.get('account.analytic.line').write(cr, uid, [eng_line.id],
848+ {'amount': amount,
849+ 'amount_currency': -1 * anal_amount}, context=context)
850+ # check next G/L account or CV line
851+ break
852+ cv_to_close[cv_line[2]] = True
853+ eng_ids = self.pool.get('account.analytic.line').search(cr, uid, [('commitment_line_id', '=', cv_line[0])], context=context)
854+ if eng_ids:
855+ self.pool.get('account.analytic.line').unlink(cr, uid, eng_ids, context=context)
856+ self.pool.get('account.commitment.line').write(cr, uid, [cv_line[0]], {'amount': 0.0}, context=context)
857+ if abs(cv_line[4] - amount_dic[k]) < 0.001:
858+ # check next G/L account or CV line
859+ break
860+ amount_dic[k] -= cv_line[4]
861+
862+ if auto_cv and from_cancel and from_cancel is not True:
863 # we cancel the last IN from PO and no draft invoice exist
864 if not self.pool.get('account.invoice').search_exist(cr, uid, [('purchase_ids', 'in', po_ids), ('state', '=', 'draft')], context=context):
865 dpo_ids = self.pool.get('purchase.order').search(cr, uid, [('id', 'in', po_ids), ('po_version', '!=', 1), ('order_type', '=', 'direct')], context=context)
866
867=== modified file 'bin/addons/analytic_distribution_supply/stock.py'
868--- bin/addons/analytic_distribution_supply/stock.py 2018-09-04 17:49:19 +0000
869+++ bin/addons/analytic_distribution_supply/stock.py 2021-08-11 08:19:49 +0000
870@@ -23,30 +23,6 @@
871
872 from osv import osv
873
874-class stock_picking(osv.osv):
875- _name = 'stock.picking'
876- _inherit = 'stock.picking'
877-
878-
879- def _invoice_hook(self, cr, uid, picking, invoice_id):
880- """
881- Create a link between invoice and purchase_order.
882- Copy analytic distribution from purchase order to invoice (or from commitment voucher if exists)
883- """
884- if invoice_id and picking:
885- po_id = picking.purchase_id and picking.purchase_id.id or False
886- so_id = picking.sale_id and picking.sale_id.id or False
887- if po_id:
888- self.pool.get('purchase.order').write(cr, uid, [po_id], {'invoice_ids': [(4, invoice_id)]})
889- if so_id:
890- self.pool.get('sale.order').write(cr, uid, [so_id], {'invoice_ids': [(4, invoice_id)]})
891- # Copy analytic distribution from purchase order or commitment voucher (if exists) or sale order
892- self.pool.get('account.invoice').fetch_analytic_distribution(cr, uid, [invoice_id])
893- return super(stock_picking, self)._invoice_hook(cr, uid, picking, invoice_id)
894-
895-# action_invoice_create method have been removed because of impossibility to retrieve DESTINATION from SO.
896-
897-stock_picking()
898
899 class stock_move(osv.osv):
900 _name = 'stock.move'
901@@ -64,6 +40,7 @@
902
903 inv_obj = self.pool.get('account.invoice')
904 account_amount = {}
905+ cvl_amount = {}
906 po_ids = {}
907 for move in self.browse(cr, uid, ids, context=context):
908 # Fetch all necessary elements
909@@ -80,14 +57,21 @@
910 continue
911
912 po_ids[move.purchase_line_id.order_id.id] = True
913- account_id = inv_obj._get_expense_account(cr, uid, move.purchase_line_id, context=context)
914- if account_id:
915- if account_id not in account_amount:
916- account_amount[account_id] = 0
917- account_amount[account_id] += round(qty * price_unit, 2)
918-
919- if account_amount and po_ids:
920- inv_obj._update_commitments_lines(cr, uid, po_ids.keys(), account_amount, from_cancel=ids, context=context)
921+ cv_line = move.purchase_line_id.cv_line_ids and move.purchase_line_id.cv_line_ids[0] or False
922+ cv_version = cv_line and cv_line.commit_id and cv_line.commit_id.version or 1
923+ if cv_version > 1:
924+ if cv_line.id not in cvl_amount:
925+ cvl_amount[cv_line.id] = 0
926+ cvl_amount[cv_line.id] += round(qty * price_unit, 2)
927+ else:
928+ account_id = inv_obj._get_expense_account(cr, uid, move.purchase_line_id, context=context)
929+ if account_id:
930+ if account_id not in account_amount:
931+ account_amount[account_id] = 0
932+ account_amount[account_id] += round(qty * price_unit, 2)
933+ if (account_amount or cvl_amount) and po_ids:
934+ inv_obj._update_commitments_lines(cr, uid, po_ids.keys(), account_amount_dic=account_amount, cvl_amount_dic=cvl_amount,
935+ from_cancel=ids, context=context)
936
937 return super(stock_move, self).action_cancel(cr, uid, ids, context=context)
938
939
940=== modified file 'bin/addons/base/res/res_log.py'
941--- bin/addons/base/res/res_log.py 2018-08-06 13:07:33 +0000
942+++ bin/addons/base/res/res_log.py 2021-08-11 08:19:49 +0000
943@@ -55,6 +55,8 @@
944 create_context = context and dict(context) or {}
945 if 'res_log_read' in create_context:
946 vals['read'] = create_context.pop('res_log_read')
947+ if '__copy_data_seen' in create_context:
948+ create_context.pop('__copy_data_seen')
949 if create_context and not vals.get('context'):
950 vals['context'] = create_context
951 return super(res_log, self).create(cr, uid, vals, context=context)
952
953=== modified file 'bin/addons/delivery_mechanism/delivery_mechanism.py'
954=== modified file 'bin/addons/msf_profile/data/patches.xml'
955--- bin/addons/msf_profile/data/patches.xml 2021-08-05 13:10:04 +0000
956+++ bin/addons/msf_profile/data/patches.xml 2021-08-11 08:19:49 +0000
957@@ -677,6 +677,7 @@
958 <field name="method">us_8753_admin_never_expire_password</field>
959 </record>
960
961+<<<<<<< TREE
962 <!-- UF22.0 -->
963 <record id="us_8805_product_set_archived" model="patch.scripts">
964 <field name="method">us_8805_product_set_archived</field>
965@@ -686,5 +687,11 @@
966 <field name="method">us_8869_remove_ir_import</field>
967 </record>
968
969+=======
970+ <!-- UF22.0 -->
971+ <record id="us_7449_set_cv_version" model="patch.scripts">
972+ <field name="method">us_7449_set_cv_version</field>
973+ </record>
974+>>>>>>> MERGE-SOURCE
975 </data>
976 </openerp>
977
978=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
979--- bin/addons/msf_profile/i18n/fr_MF.po 2021-08-11 05:45:06 +0000
980+++ bin/addons/msf_profile/i18n/fr_MF.po 2021-08-11 08:19:49 +0000
981@@ -3925,6 +3925,7 @@
982 #: field:account.invoice.tax,manual:0
983 #: view:account.commitment:0
984 #: selection:account.commitment,type:0
985+#: selection:account.commitment.line,commit_type:0
986 #: selection:purchase.order,invoice_method:0
987 #: model:ir.ui.menu,name:sync_client.sync_wiz_menu
988 msgid "Manual"
989@@ -9634,6 +9635,7 @@
990
991 #. module: analytic_distribution
992 #: selection:account.commitment,type:0
993+#: selection:account.commitment.line,commit_type:0
994 msgid "Manual - ESC supplier"
995 msgstr "Manuel - Fournisseur ESC"
996
997@@ -30297,7 +30299,7 @@
998 #: code:addons/purchase/purchase_order_line.py:1795
999 #, python-format
1000 msgid "There is no expense account defined for this product: \"%s\" (id:%d)"
1001-msgstr "Il n'y a pas de compte de charge définit pour ce produit : \"%s\" (id. : %d)"
1002+msgstr "Il n'y a pas de compte de charge défini pour ce produit : \"%s\" (id. : %d)"
1003
1004 #. module: msf_outgoing
1005 #: report:packing.list:0
1006@@ -30674,7 +30676,7 @@
1007 #: code:addons/analytic_distribution_supply/invoice.py:147
1008 #, python-format
1009 msgid "There is no expense account defined for this PO line: \"%s\" (id:%d)"
1010-msgstr "There is no expense account defined for this PO line: \"%s\" (id:%d)"
1011+msgstr "Il n'y a pas de compte de charge défini pour cette ligne de BC : \"%s\" (id. : %d)"
1012
1013 #. module: msf_doc_import
1014 #: view:msf.import.export:0
1015@@ -44516,12 +44518,6 @@
1016 msgid "Choose day"
1017 msgstr "Choisir un jour"
1018
1019-#. module: analytic_distribution
1020-#: code:addons/analytic_distribution/account_commitment.py:161
1021-#, python-format
1022-msgid "No analytic distribution found for %s %s"
1023-msgstr "Pas de distribution analytique trouvée pour %s %s"
1024-
1025 #. modules: sync_so, analytic_distribution_supply, analytic_override, analytic_distribution
1026 #: code:addons/analytic_distribution/wizard/analytic_distribution_wizard.py:322
1027 #: code:addons/analytic_distribution/wizard/analytic_distribution_wizard.py:352
1028@@ -47492,6 +47488,7 @@
1029 #: view:account.bank.statement.line:0
1030 #: view:sale.order.line:0
1031 #: view:free.allocation.wizard:0
1032+#: view:account.commitment.line:0
1033 msgid "Delete"
1034 msgstr "Supprimer"
1035
1036@@ -68111,7 +68108,7 @@
1037 #: code:addons/analytic_distribution_supply/invoice.py:227
1038 #, python-format
1039 msgid "No analytic distribution found."
1040-msgstr "Pas de disribution analytique trouvée."
1041+msgstr "Pas de distribution analytique trouvée."
1042
1043 #. module: base
1044 #: model:res.currency,currency_name:base.CHF
1045@@ -69423,9 +69420,32 @@
1046
1047 #. module: analytic_distribution
1048 #: selection:account.commitment,type:0
1049+#: selection:account.commitment.line,commit_type:0
1050 msgid "Automatic - External supplier"
1051 msgstr "Automatique - Fournisseur Externe"
1052
1053+#. module: analytic_distribution
1054+#: selection:account.commitment,type:0
1055+#: selection:account.commitment.line,commit_type:0
1056+msgid "Automatic - Intermission"
1057+msgstr "Automatique - Intermission"
1058+
1059+#. module: analytic_distribution
1060+#: selection:account.commitment,type:0
1061+#: selection:account.commitment.line,commit_type:0
1062+msgid "Automatic - Intersection"
1063+msgstr "Automatique - Intersection"
1064+
1065+#. module: analytic_distribution
1066+#: view:account.commitment:0
1067+msgid "Intermission Commitment Voucher"
1068+msgstr "Bon d'Engagement Intermission"
1069+
1070+#. module: analytic_distribution
1071+#: view:account.commitment:0
1072+msgid "Intersection Commitment Voucher"
1073+msgstr "Bon d'Engagement Intersection"
1074+
1075 #. module: procurement
1076 #: field:procurement.order,close_move:0
1077 msgid "Close Move at end"
1078@@ -82184,7 +82204,7 @@
1079 msgid "SFTP connection succeeded"
1080 msgstr "SFTP connection succeeded"
1081
1082-#. modules: tender_flow, product_nomenclature, product_asset, account_override, product_attributes, register_accounting, product_expiry, procurement_cycle, return_claim, supplier_catalogue, import_data, mission_stock, unifield_setup, stock_forecast, stock_batch_recall, order_types, msf_doc_import, purchase_followup, product, stock_override, stock_schedule, service_purchasing, consumption_calculation, purchase_override, specific_rules, kit, base, product_list, product_manufacturer, procurement_report, threshold_value, purchase, account, msf_outgoing, stock_move_tracking, purchase_allocation_report, procurement_auto, sale, transport_mgmt, procurement, sourcing, msf_audittrail, purchase_msf, stock, sync_so, msf_tools
1083+#. modules: tender_flow, product_nomenclature, product_asset, account_override, product_attributes, register_accounting, product_expiry, procurement_cycle, return_claim, supplier_catalogue, import_data, mission_stock, unifield_setup, stock_forecast, stock_batch_recall, order_types, msf_doc_import, purchase_followup, product, stock_override, stock_schedule, service_purchasing, consumption_calculation, purchase_override, specific_rules, kit, base, product_list, product_manufacturer, procurement_report, threshold_value, purchase, account, msf_outgoing, stock_move_tracking, purchase_allocation_report, procurement_auto, sale, transport_mgmt, procurement, sourcing, msf_audittrail, purchase_msf, stock, sync_so, msf_tools, analytic_distribution
1084 #: field:account.analytic.line,product_id:0
1085 #: view:account.entries.report:0
1086 #: field:account.entries.report,product_id:0
1087@@ -82390,6 +82410,7 @@
1088 #: view:replenishment.segment.line.amc.past_fmc:0
1089 #: view:replenishment.segment.line.min_max_auto_supply.history:0
1090 #: field:view.expired.expiring.stock.lines,product_id:0
1091+#: field:account.commitment.line,po_line_product_id:0
1092 #, python-format
1093 msgid "Product"
1094 msgstr "Produit"
1095@@ -87510,7 +87531,7 @@
1096 msgstr "Ceci est utilisé uniquement si vous sélectionnez une localisation de type chainée.\n"
1097 "La valeur 'Mouvement automatique' créera un mouvement de stock après le mouvement actuel qui sera validé automatiquement. La valeur 'Opération manuelle', le mouvement de stock devra être validé par un opérateur. La valeur 'Automatique sans étape supplémentaire', la localisation est remplacée sur le mouvement d'origine."
1098
1099-#. modules: msf_budget, sync_client, update_client, kit, base, msf_profile
1100+#. modules: msf_budget, sync_client, update_client, kit, base, msf_profile, analytic_distribution
1101 #: view:ir.module.module:0
1102 #: report:ir.module.reference:0
1103 #: view:composition.kit:0
1104@@ -87528,6 +87549,7 @@
1105 #: field:sync.client.update_to_send,version:0
1106 #: field:sync.version.instance.monitor,version:0
1107 #: field:sync_client.version,name:0
1108+#: field:account.commitment,version:0
1109 msgid "Version"
1110 msgstr "Version"
1111
1112@@ -91254,13 +91276,21 @@
1113 msgid "Purchase Orders Waiting Confirmation"
1114 msgstr "Bons de Commandes en attente de Confirmation"
1115
1116-#. modules: purchase, analytic_distribution, purchase_override
1117+#. modules: purchase, analytic_distribution, purchase_override, account_override, register_accounting
1118 #: field:account.commitment,line_ids:0
1119 #: view:account.commitment.line:0
1120+#: field:purchase.order.line,cv_line_ids:0
1121+#: field:purchase.order.merged.line,cv_line_ids:0
1122+#: field:account.invoice.line,cv_line_ids:0
1123+#: field:wizard.account.invoice.line,cv_line_ids:0
1124+msgid "Commitment Voucher Lines"
1125+msgstr "Lignes de Bon d'Engagement"
1126+
1127+#. modules: purchase, purchase_override
1128 #: field:purchase.order.line,commitment_line_ids:0
1129 #: field:purchase.order.merged.line,commitment_line_ids:0
1130-msgid "Commitment Voucher Lines"
1131-msgstr "Lignes de Bon d'Engagement"
1132+msgid "Commitment Voucher Lines (deprecated)"
1133+msgstr "Lignes de Bon d'Engagement (obsolète)"
1134
1135 #. module: sync_client
1136 #: code:addons/sync_client/hq_monitor.py:48
1137@@ -98351,13 +98381,17 @@
1138 msgstr "Type de note"
1139
1140 #. modules: purchase, analytic_distribution, msf_doc_import, finance
1141-#: field:account.commitment.line,purchase_order_line_ids:0
1142 #: view:purchase.order.line:0
1143 #: field:wizard.import.po,line_ids:0
1144 #: field:account.invoice.line,purchase_order_line_ids:0
1145 msgid "Purchase Order Lines"
1146 msgstr "Lignes Bon de Commande"
1147
1148+#. module: analytic_distribution
1149+#: field:account.commitment.line,purchase_order_line_ids:0
1150+msgid "Purchase Order Lines (deprecated)"
1151+msgstr "Lignes Bon de Commande (obsolète)"
1152+
1153 #. module: specific_rules
1154 #: field:unconsistent.stock.report.line,product_bn:0
1155 msgid "BN management"
1156@@ -112379,6 +112413,7 @@
1157 #, python-format
1158 msgid "The Pre-Packing List is using a deactivated Delivery Address (%s). Please select another one to be able to process."
1159 msgstr "La Liste de Pré-Colisage utilise une Addresse de Livraison désactivée (%s). Veuillez en sélectionner une autre pour pouvoir continuer le traitement."
1160+<<<<<<< TREE
1161
1162 #. module: sync_so
1163 #: code:addons/sync_so/so_po_common.py:491
1164@@ -112719,3 +112754,47 @@
1165 #, python-format
1166 msgid "Products moved in the last: %s month%s"
1167 msgstr "Mouvements de stock dans: %s dernier%s mois"
1168+=======
1169+
1170+#. module: sync_so
1171+#: code:addons/sync_so/so_po_common.py:491
1172+#, python-format
1173+msgid "Cannot process Document/line due to Product Code %s which does not exist in this instance"
1174+msgstr "Impossible de traiter le Document/la ligne. Le Code Produit %s n'existe pas dans cette instance"
1175+
1176+#. module: analytic_distribution
1177+#: help:account.commitment,version:0
1178+msgid "Technical field to distinguish old CVs from new ones which have a different behavior."
1179+msgstr "Champ technique pour distinguer les anciens Bons d'Engagement des nouveaux qui ont un comportement différent."
1180+
1181+#. module: analytic_distribution
1182+#: field:account.commitment,display_super_done_button:0
1183+msgid "Display the button allowing to always set a CV to Done"
1184+msgstr "Afficher le bouton permettant de toujours passer un Bon d'Engagement en Terminé"
1185+
1186+#. module: analytic_distribution
1187+#: field:account.commitment.line,commit_type:0
1188+msgid "Commitment Voucher Type"
1189+msgstr "Type de Bon d'Engagement"
1190+
1191+#. module: analytic_distribution
1192+#: field:account.commitment.line,po_line_id:0
1193+#: field:account.commitment.line,po_line_number:0
1194+msgid "PO Line"
1195+msgstr "Ligne du BC"
1196+
1197+#. module: analytic_distribution
1198+#: view:account.commitment:0
1199+msgid "Done (for Administrator only)"
1200+msgstr "Terminé (pour l'Administrateur uniquement)"
1201+
1202+#. module: analytic_distribution
1203+#: view:account.commitment:0
1204+msgid "You are about to set this Commitment Voucher to the state \"Done\". Do you want to proceed?"
1205+msgstr "Vous êtes sur le point de passer ce Bon d'Engagement à l'état \"Terminé\". Voulez-vous continuer ?"
1206+
1207+#. module: analytic_distribution
1208+#: view:account.commitment.line:0
1209+msgid "Do you really want to delete this line?"
1210+msgstr "Voulez-vous vraiment supprimer cette ligne ?"
1211+>>>>>>> MERGE-SOURCE
1212
1213=== modified file 'bin/addons/msf_profile/msf_profile.py'
1214--- bin/addons/msf_profile/msf_profile.py 2021-08-09 18:09:28 +0000
1215+++ bin/addons/msf_profile/msf_profile.py 2021-08-11 08:19:49 +0000
1216@@ -53,6 +53,7 @@
1217 'model': lambda *a: 'patch.scripts',
1218 }
1219
1220+<<<<<<< TREE
1221 # UF22.0
1222 def us_8805_product_set_archived(self, cr, uid, *a, **b):
1223 if self.pool.get('sync_client.version') and self.pool.get('sync.client.entity'):
1224@@ -80,6 +81,18 @@
1225 cr.execute("update internal_request_import set file_to_import=NULL");
1226 return True
1227
1228+=======
1229+ # UF22.0
1230+ def us_7449_set_cv_version(self, cr, uid, *a, **b):
1231+ """
1232+ Sets the existing Commitment Vouchers in version 1.
1233+ """
1234+ if self.pool.get('sync.client.entity'): # existing instances
1235+ cr.execute("UPDATE account_commitment SET version = 1")
1236+ self._logger.warn('Commitment Vouchers: %s CV(s) set to version 1.', cr.rowcount)
1237+ return True
1238+
1239+>>>>>>> MERGE-SOURCE
1240 # UF21.1
1241 def us_8810_fake_updates(self, cr, uid, *a, **b):
1242 if self.pool.get('sync.client.entity'):
1243
1244=== modified file 'bin/addons/purchase/purchase_order.py'
1245--- bin/addons/purchase/purchase_order.py 2021-08-10 16:21:09 +0000
1246+++ bin/addons/purchase/purchase_order.py 2021-08-11 08:19:49 +0000
1247@@ -2887,12 +2887,21 @@
1248 ('instance_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context).company_id.instance_id.id)
1249 ], limit=1, context=context)
1250
1251+ po_partner_type = po.partner_id.partner_type
1252+ if po_partner_type == 'external':
1253+ cv_type = 'external'
1254+ elif po_partner_type == 'section':
1255+ cv_type = 'intersection'
1256+ elif po_partner_type == 'intermission':
1257+ cv_type = 'intermission'
1258+ else:
1259+ cv_type = 'manual'
1260 vals = {
1261 'journal_id': engagement_ids and engagement_ids[0] or False,
1262 'currency_id': po.currency_id and po.currency_id.id or False,
1263 'partner_id': po.partner_id and po.partner_id.id or False,
1264 'purchase_id': po.id or False,
1265- 'type': 'external' if po.partner_id.partner_type == 'external' else 'manual',
1266+ 'type': cv_type,
1267 }
1268 # prepare some values
1269 period_ids = get_period_from_date(self, cr, uid, cv_date, context=context)
1270
1271=== modified file 'bin/addons/purchase/purchase_order_line.py'
1272--- bin/addons/purchase/purchase_order_line.py 2021-08-10 16:13:59 +0000
1273+++ bin/addons/purchase/purchase_order_line.py 2021-08-11 08:19:49 +0000
1274@@ -568,8 +568,12 @@
1275 # finance
1276 'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution'),
1277 'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean', string='Header Distrib.?'),
1278+ # for CV in version 1
1279 'commitment_line_ids': fields.many2many('account.commitment.line', 'purchase_line_commitment_rel', 'purchase_id', 'commitment_id',
1280- string="Commitment Voucher Lines", readonly=True),
1281+ string="Commitment Voucher Lines (deprecated)", readonly=True),
1282+ # for CV starting from version 2
1283+ # note: cv_line_ids is a o2m because of the related m2o on CV lines but it should only contain one CV line
1284+ 'cv_line_ids': fields.one2many('account.commitment.line', 'po_line_id', string="Commitment Voucher Lines"),
1285 'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
1286 selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
1287 string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
1288@@ -1295,7 +1299,7 @@
1289 self.pool.get('product.product')._get_restriction_error(cr, uid, [pol.product_id.id],
1290 {'partner_id': pol.order_id.partner_id.id}, context=context)
1291
1292- default.update({'state': 'draft', 'move_ids': [], 'invoiced': 0, 'invoice_lines': [], 'commitment_line_ids': [], })
1293+ default.update({'state': 'draft', 'move_ids': [], 'invoiced': 0, 'invoice_lines': [], 'commitment_line_ids': [], 'cv_line_ids': [], })
1294
1295 for field in ['origin', 'move_dest_id', 'original_product', 'original_qty', 'original_price', 'original_uom', 'original_currency_id', 'modification_comment', 'sync_linked_sol', 'created_by_vi_import', 'external_ref']:
1296 if field not in default:
1297@@ -1868,14 +1872,17 @@
1298
1299 import_commitments = self.pool.get('unifield.setup.configuration').get_config(cr, uid).import_commitments
1300 for pol in self.browse(cr, uid, ids, context=context):
1301- # only create CV for external and ESC partners:
1302- if pol.order_id.partner_id.partner_type not in ['external', 'esc']:
1303+ if pol.order_id.partner_id.partner_type == 'internal':
1304 return False
1305
1306 if pol.order_id.partner_id.partner_type == 'esc' and import_commitments:
1307 return False
1308
1309- if pol.order_id.order_type in ['loan', 'in_kind']:
1310+ if pol.order_id.order_type in ['loan', 'in_kind', 'donation_st', 'donation_exp']:
1311+ return False
1312+
1313+ # exclude push flow (FO or FO line created first)
1314+ if pol.order_id.push_fo or pol.set_as_sourced_n:
1315 return False
1316
1317 commitment_voucher_id = self.pool.get('account.commitment').search(cr, uid, [('purchase_id', '=', pol.order_id.id), ('state', '=', 'draft')], context=context)
1318@@ -1886,42 +1893,63 @@
1319 raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
1320 commitment_voucher_id = self.pool.get('purchase.order').create_commitment_voucher_from_po(cr, uid, [pol.order_id.id], cv_date=pol.confirmed_delivery_date, context=context)
1321
1322- # group PO line by account_id:
1323 expense_account = pol.account_4_distribution and pol.account_4_distribution.id or False
1324 if not expense_account:
1325 raise osv.except_osv(_('Error'), _('There is no expense account defined for this line: %s (id:%d)') % (pol.name or '', pol.id))
1326
1327+ # in CV in version 1, PO lines are grouped by account_id. Else 1 PO line generates 1 CV line.
1328+ cv_version = self.pool.get('account.commitment').read(cr, uid, commitment_voucher_id, ['version'], context=context)['version']
1329+ cc_lines = []
1330+ ad_header = [] # if filled in, the line itself has no AD but uses the one at header level
1331 if pol.analytic_distribution_id:
1332 cc_lines = pol.analytic_distribution_id.cost_center_lines
1333- else:
1334+ elif cv_version < 2:
1335+ # in CV in version 1, if there is no AD on the PO line, the AD at PO header level is used at CV line level
1336 cc_lines = pol.order_id.analytic_distribution_id.cost_center_lines
1337+ else:
1338+ ad_header = pol.order_id.analytic_distribution_id.cost_center_lines
1339
1340- if not cc_lines:
1341+ if not cc_lines and not ad_header:
1342 raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for %s on the line %s for the product %s! It must be added manually.')
1343 % (pol.order_id.name, pol.line_number, pol.product_id and pol.product_id.default_code or pol.name or ''))
1344
1345-
1346- commit_line_id = self.pool.get('account.commitment.line').search(cr, uid, [('commit_id', '=', commitment_voucher_id), ('account_id', '=', expense_account)], context=context)
1347- if not commit_line_id: # create new commitment line
1348- distrib_id = self.pool.get('analytic.distribution').create(cr, uid, {}, context=context)
1349- commit_line_id = self.pool.get('account.commitment.line').create(cr, uid, {
1350+ new_cv_line = False
1351+ if cv_version > 1:
1352+ new_cv_line = True
1353+ else:
1354+ commit_line_id = self.pool.get('account.commitment.line').search(cr, uid,
1355+ [('commit_id', '=', commitment_voucher_id),
1356+ ('account_id', '=', expense_account)], context=context)
1357+ if not commit_line_id:
1358+ new_cv_line = True
1359+ if new_cv_line: # create new commitment line
1360+ if ad_header: # the line has no AD itself, it uses the AD at header level
1361+ distrib_id = False
1362+ else:
1363+ distrib_id = self.pool.get('analytic.distribution').create(cr, uid, {}, context=context)
1364+ commit_line_vals = {
1365 'commit_id': commitment_voucher_id,
1366 'account_id': expense_account,
1367 'amount': pol.price_subtotal,
1368 'initial_amount': pol.price_subtotal,
1369- 'purchase_order_line_ids': [(4, pol.id)],
1370 'analytic_distribution_id': distrib_id,
1371- }, context=context)
1372- for aline in cc_lines:
1373- vals = {
1374- 'distribution_id': distrib_id,
1375- 'analytic_id': aline.analytic_id.id,
1376- 'currency_id': pol.order_id.currency_id.id,
1377- 'destination_id': aline.destination_id.id,
1378- 'percentage': aline.percentage,
1379- }
1380- self.pool.get('cost.center.distribution.line').create(cr, uid, vals, context=context)
1381- self.pool.get('analytic.distribution').create_funding_pool_lines(cr, uid, [distrib_id], expense_account, context=context)
1382+ }
1383+ if cv_version > 1:
1384+ commit_line_vals.update({'po_line_id': pol.id, })
1385+ else:
1386+ commit_line_vals.update({'purchase_order_line_ids': [(4, pol.id)], })
1387+ commit_line_id = self.pool.get('account.commitment.line').create(cr, uid, commit_line_vals, context=context)
1388+ if distrib_id:
1389+ for aline in cc_lines:
1390+ vals = {
1391+ 'distribution_id': distrib_id,
1392+ 'analytic_id': aline.analytic_id.id,
1393+ 'currency_id': pol.order_id.currency_id.id,
1394+ 'destination_id': aline.destination_id.id,
1395+ 'percentage': aline.percentage,
1396+ }
1397+ self.pool.get('cost.center.distribution.line').create(cr, uid, vals, context=context)
1398+ self.pool.get('analytic.distribution').create_funding_pool_lines(cr, uid, [distrib_id], expense_account, context=context)
1399
1400 else: # update existing commitment line:
1401 commit_line_id = commit_line_id[0]
1402
1403=== modified file 'bin/addons/purchase/stock.py'
1404--- bin/addons/purchase/stock.py 2020-09-25 15:16:18 +0000
1405+++ bin/addons/purchase/stock.py 2021-08-11 08:19:49 +0000
1406@@ -34,58 +34,6 @@
1407 'purchase_id': False,
1408 }
1409
1410- def _get_address_invoice(self, cr, uid, picking):
1411- """ Gets invoice address of a partner
1412- @return {'contact': address, 'invoice': address} for invoice
1413- """
1414- res = super(stock_picking, self)._get_address_invoice(cr, uid, picking)
1415- if picking.purchase_id:
1416- partner_obj = self.pool.get('res.partner')
1417- partner = picking.purchase_id.partner_id or picking.address_id.partner_id
1418- data = partner_obj.address_get(cr, uid, [partner.id],
1419- ['contact', 'invoice'])
1420- res.update(data)
1421- return res
1422-
1423- def get_currency_id(self, cursor, user, picking):
1424- if picking.purchase_id:
1425- return picking.purchase_id.pricelist_id.currency_id.id
1426- else:
1427- return super(stock_picking, self).get_currency_id(cursor, user, picking)
1428-
1429- def _get_comment_invoice(self, cursor, user, picking):
1430- if picking.purchase_id and picking.purchase_id.notes:
1431- if picking.note:
1432- return picking.note + '\n' + picking.purchase_id.notes
1433- else:
1434- return picking.purchase_id.notes
1435- return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)
1436-
1437- def _get_price_unit_invoice(self, cursor, user, move_line, type):
1438- if move_line.purchase_line_id:
1439- return move_line.purchase_line_id.price_unit
1440- return super(stock_picking, self)._get_price_unit_invoice(cursor, user, move_line, type)
1441-
1442- def _get_discount_invoice(self, cursor, user, move_line):
1443- if move_line.purchase_line_id:
1444- return 0.0
1445- return super(stock_picking, self)._get_discount_invoice(cursor, user, move_line)
1446-
1447- def _get_taxes_invoice(self, cursor, user, move_line, type):
1448- if move_line.purchase_line_id:
1449- return [x.id for x in move_line.purchase_line_id.taxes_id]
1450- return super(stock_picking, self)._get_taxes_invoice(cursor, user, move_line, type)
1451-
1452- def _get_account_analytic_invoice(self, cursor, user, picking, move_line):
1453- if move_line.purchase_line_id:
1454- return move_line.purchase_line_id.account_analytic_id.id
1455- return super(stock_picking, self)._get_account_analytic_invoice(cursor, user, picking, move_line)
1456-
1457- def _invoice_hook(self, cursor, user, picking, invoice_id):
1458- purchase_obj = self.pool.get('purchase.order')
1459- if picking.purchase_id:
1460- purchase_obj.write(cursor, user, [picking.purchase_id.id], {'invoice_id': invoice_id,})
1461- return super(stock_picking, self)._invoice_hook(cursor, user, picking, invoice_id)
1462
1463 stock_picking()
1464
1465
1466=== modified file 'bin/addons/sale/stock.py'
1467--- bin/addons/sale/stock.py 2019-09-18 14:06:52 +0000
1468+++ bin/addons/sale/stock.py 2021-08-11 08:19:49 +0000
1469@@ -40,65 +40,6 @@
1470 'sale_id': False
1471 }
1472
1473- def get_currency_id(self, cursor, user, picking):
1474- if picking.sale_id:
1475- return picking.sale_id.pricelist_id.currency_id.id
1476- else:
1477- return super(stock_picking, self).get_currency_id(cursor, user, picking)
1478-
1479- def _get_payment_term(self, cursor, user, picking):
1480- if picking.sale_id and picking.sale_id.payment_term:
1481- return picking.sale_id.payment_term.id
1482- return super(stock_picking, self)._get_payment_term(cursor, user, picking)
1483-
1484- def _get_address_invoice(self, cursor, user, picking):
1485- res = {}
1486- if picking.sale_id:
1487- res['contact'] = picking.sale_id.partner_order_id.id
1488- res['invoice'] = picking.sale_id.partner_invoice_id.id
1489- return res
1490- return super(stock_picking, self)._get_address_invoice(cursor, user, picking)
1491-
1492- def _get_comment_invoice(self, cursor, user, picking):
1493- if picking.note or (picking.sale_id and picking.sale_id.note):
1494- return picking.note or picking.sale_id.note
1495- return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)
1496-
1497- def _get_price_unit_invoice(self, cursor, user, move_line, type):
1498- if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
1499- uom_id = move_line.product_id.uom_id.id
1500- uos_id = move_line.product_id.uos_id and move_line.product_id.uos_id.id or False
1501- price = move_line.sale_line_id.price_unit
1502- coeff = move_line.product_id.uos_coeff
1503- if uom_id != uos_id and coeff != 0:
1504- price_unit = price / coeff
1505- return price_unit
1506- return move_line.sale_line_id.price_unit
1507- return super(stock_picking, self)._get_price_unit_invoice(cursor, user, move_line, type)
1508-
1509- def _get_discount_invoice(self, cursor, user, move_line):
1510- if move_line.sale_line_id:
1511- return move_line.sale_line_id.discount
1512- return super(stock_picking, self)._get_discount_invoice(cursor, user, move_line)
1513-
1514- def _get_taxes_invoice(self, cursor, user, move_line, type):
1515- if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
1516- return [x.id for x in move_line.sale_line_id.tax_id]
1517- return super(stock_picking, self)._get_taxes_invoice(cursor, user, move_line, type)
1518-
1519- def _get_account_analytic_invoice(self, cursor, user, picking, move_line):
1520- if picking.sale_id:
1521- return picking.sale_id.project_id.id
1522- return super(stock_picking, self)._get_account_analytic_invoice(cursor, user, picking, move_line)
1523-
1524- def _invoice_hook(self, cursor, user, picking, invoice_id):
1525- sale_obj = self.pool.get('sale.order')
1526- if picking.sale_id:
1527- sale_obj.write(cursor, user, [picking.sale_id.id], {
1528- 'invoice_ids': [(4, invoice_id)],
1529- })
1530- return super(stock_picking, self)._invoice_hook(cursor, user, picking, invoice_id)
1531-
1532
1533 stock_picking()
1534
1535
1536=== modified file 'bin/addons/stock/stock.py'
1537--- bin/addons/stock/stock.py 2021-08-09 14:13:09 +0000
1538+++ bin/addons/stock/stock.py 2021-08-11 08:19:49 +0000
1539@@ -1193,12 +1193,20 @@
1540 return True
1541
1542 def get_currency_id(self, cr, uid, picking):
1543- return False
1544+ if picking.sale_id:
1545+ return picking.sale_id.pricelist_id.currency_id.id
1546+ else:
1547+ if picking.purchase_id:
1548+ return picking.purchase_id.pricelist_id.currency_id.id
1549+ else:
1550+ return False
1551
1552 def _get_payment_term(self, cr, uid, picking):
1553 """ Gets payment term from partner.
1554 @return: Payment term
1555 """
1556+ if picking.sale_id and picking.sale_id.payment_term:
1557+ return picking.sale_id.payment_term.id
1558 partner = picking.address_id.partner_id
1559 return partner.property_payment_term and partner.property_payment_term.id or False
1560
1561@@ -1206,37 +1214,85 @@
1562 """ Gets invoice address of a partner
1563 @return {'contact': address, 'invoice': address} for invoice
1564 """
1565+ res = {}
1566 partner_obj = self.pool.get('res.partner')
1567+ if picking.sale_id:
1568+ res['contact'] = picking.sale_id.partner_order_id.id
1569+ res['invoice'] = picking.sale_id.partner_invoice_id.id
1570+ return res
1571 partner = picking.address_id.partner_id
1572- return partner_obj.address_get(cr, uid, [partner.id],
1573- ['contact', 'invoice'])
1574+ res = partner_obj.address_get(cr, uid, [partner.id], ['contact', 'invoice'])
1575+ if picking.purchase_id:
1576+ partner = picking.purchase_id.partner_id or picking.address_id.partner_id
1577+ data = partner_obj.address_get(cr, uid, [partner.id], ['contact', 'invoice'])
1578+ res.update(data)
1579+ return res
1580
1581 def _get_comment_invoice(self, cr, uid, picking):
1582 """
1583 @return: comment string for invoice
1584 """
1585+ if picking.note or (picking.sale_id and picking.sale_id.note):
1586+ return picking.note or picking.sale_id.note
1587+ if picking.purchase_id and picking.purchase_id.notes:
1588+ if picking.note:
1589+ return picking.note + '\n' + picking.purchase_id.notes
1590+ else:
1591+ return picking.purchase_id.notes
1592 return picking.note or ''
1593
1594 def _get_price_unit_invoice(self, cr, uid, move_line, type, context=None):
1595 """ Gets price unit for invoice
1596+ Updates the Unit price according to the UoM received and the UoM ordered
1597 @param move_line: Stock move lines
1598 @param type: Type of invoice
1599 @return: The price unit for the move line
1600 """
1601 if context is None:
1602 context = {}
1603-
1604- if type in ('in_invoice', 'in_refund'):
1605- # Take the user company and pricetype
1606- context['currency_id'] = move_line.company_id.currency_id.id
1607- amount_unit = move_line.product_id.price_get('standard_price', context)[move_line.product_id.id]
1608- return amount_unit
1609- else:
1610- return move_line.product_id.list_price
1611+ res = None
1612+ if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
1613+ uom_id = move_line.product_id.uom_id.id
1614+ uos_id = move_line.product_id.uos_id and move_line.product_id.uos_id.id or False
1615+ price = move_line.sale_line_id.price_unit
1616+ coeff = move_line.product_id.uos_coeff
1617+ if uom_id != uos_id and coeff != 0:
1618+ price_unit = price / coeff
1619+ res = price_unit
1620+ else:
1621+ res = move_line.sale_line_id.price_unit
1622+ if res is None:
1623+ if move_line.purchase_line_id:
1624+ res = move_line.purchase_line_id.price_unit
1625+ else:
1626+ if type in ('in_invoice', 'in_refund'):
1627+ # Take the user company and pricetype
1628+ context['currency_id'] = move_line.company_id.currency_id.id
1629+ amount_unit = move_line.product_id.price_get('standard_price', context)[move_line.product_id.id]
1630+ res = amount_unit
1631+ else:
1632+ res = move_line.product_id.list_price
1633+ if type == 'in_refund':
1634+ if move_line.picking_id and move_line.picking_id.purchase_id:
1635+ po_line_obj = self.pool.get('purchase.order.line')
1636+ po_line_id = po_line_obj.search(cr, uid, [('order_id', '=', move_line.picking_id.purchase_id.id),
1637+ ('product_id', '=', move_line.product_id.id),
1638+ ('state', '!=', 'cancel')
1639+ ], limit=1)
1640+ if po_line_id:
1641+ return po_line_obj.read(cr, uid, po_line_id[0], ['price_unit'])['price_unit']
1642+ if move_line.purchase_line_id:
1643+ po_uom_id = move_line.purchase_line_id.product_uom.id
1644+ move_uom_id = move_line.product_uom.id
1645+ uom_ratio = self.pool.get('product.uom')._compute_price(cr, uid, move_uom_id, 1, po_uom_id)
1646+ return res / uom_ratio
1647+ return res
1648
1649 def _get_discount_invoice(self, cr, uid, move_line):
1650 '''Return the discount for the move line'''
1651- return 0.0
1652+ if move_line.sale_line_id:
1653+ return move_line.sale_line_id.discount
1654+ return 0.0 # including if move_line.purchase_line_id
1655
1656 def _get_taxes_invoice(self, cr, uid, move_line, type):
1657 """ Gets taxes on invoice
1658@@ -1244,6 +1300,10 @@
1659 @param type: Type of invoice
1660 @return: Taxes Ids for the move line
1661 """
1662+ if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
1663+ return [x.id for x in move_line.sale_line_id.tax_id]
1664+ if move_line.purchase_line_id:
1665+ return [x.id for x in move_line.purchase_line_id.taxes_id]
1666 if type in ('in_invoice', 'in_refund'):
1667 taxes = move_line.product_id.supplier_taxes_id
1668 else:
1669@@ -1259,7 +1319,11 @@
1670 else:
1671 return map(lambda x: x.id, taxes)
1672
1673- def _get_account_analytic_invoice(self, cr, uid, picking, move_line):
1674+ def _get_account_analytic_invoice(self, picking, move_line):
1675+ if picking.sale_id:
1676+ return picking.sale_id.project_id.id
1677+ if move_line.purchase_line_id:
1678+ return move_line.purchase_line_id.account_analytic_id.id
1679 return False
1680
1681 def _invoice_line_hook(self, cr, uid, move_line, invoice_line_id, account_id):
1682@@ -1294,9 +1358,33 @@
1683 return True
1684
1685 def _invoice_hook(self, cr, uid, picking, invoice_id):
1686- '''Call after the creation of the invoice'''
1687+ """
1688+ Create a link between invoice and purchase_order.
1689+ Copy analytic distribution from purchase order to invoice (or from commitment voucher if it exists)
1690+
1691+ To call after the creation of the invoice
1692+ """
1693+ sale_obj = self.pool.get('sale.order')
1694+ purchase_obj = self.pool.get('purchase.order')
1695+ if invoice_id and picking:
1696+ po_id = picking.purchase_id and picking.purchase_id.id or False
1697+ so_id = picking.sale_id and picking.sale_id.id or False
1698+ if po_id:
1699+ self.pool.get('purchase.order').write(cr, uid, [po_id], {'invoice_ids': [(4, invoice_id)]})
1700+ if so_id:
1701+ self.pool.get('sale.order').write(cr, uid, [so_id], {'invoice_ids': [(4, invoice_id)]})
1702+ # Copy analytic distribution from purchase order or commitment voucher (if it exists) or sale order
1703+ self.pool.get('account.invoice').fetch_analytic_distribution(cr, uid, [invoice_id])
1704+ if picking.sale_id:
1705+ sale_obj.write(cr, uid, [picking.sale_id.id], {
1706+ 'invoice_ids': [(4, invoice_id)],
1707+ })
1708+ if picking.purchase_id:
1709+ purchase_obj.write(cr, uid, [picking.purchase_id.id], {'invoice_id': invoice_id, })
1710 return
1711
1712+ # action_invoice_create method has been removed because of the impossibility to retrieve DESTINATION from SO.
1713+
1714 def _get_invoice_type(self, pick):
1715 src_usage = dest_usage = None
1716 inv_type = None
1717@@ -1550,12 +1638,17 @@
1718 else:
1719 name = move_line.name
1720
1721+ cv_line = move_line and move_line.purchase_line_id and move_line.purchase_line_id.cv_line_ids and \
1722+ move_line.purchase_line_id.cv_line_ids[0] or False
1723+ cv_version = cv_line and cv_line.commit_id and cv_line.commit_id.version or 1
1724 if inv_type in ('out_invoice', 'out_refund'):
1725 account_id = move_line.product_id.product_tmpl_id.\
1726 property_account_income.id
1727 if not account_id:
1728 account_id = move_line.product_id.categ_id.\
1729 property_account_income_categ.id
1730+ elif cv_version > 1:
1731+ account_id = cv_line.account_id.id
1732 else:
1733 account_id = move_line.product_id.product_tmpl_id.\
1734 property_account_expense.id
1735@@ -1567,14 +1660,15 @@
1736 move_line, inv_type)
1737 discount = self._get_discount_invoice(cr, uid, move_line)
1738 tax_ids = self._get_taxes_invoice(cr, uid, move_line, inv_type)
1739- account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line)
1740+ account_analytic_id = self._get_account_analytic_invoice(picking, move_line)
1741
1742 #set UoS if it's a sale and the picking doesn't have one
1743 uos_id = move_line.product_uos and move_line.product_uos.id or False
1744 if not uos_id and inv_type in ('out_invoice', 'out_refund'):
1745 uos_id = move_line.product_uom.id
1746- account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
1747- invoice_line_id = invoice_line_obj.create(cr, uid, {
1748+ if cv_version < 2:
1749+ account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
1750+ inv_vals = {
1751 'name': name,
1752 'origin': origin,
1753 'invoice_id': invoice_id,
1754@@ -1586,7 +1680,10 @@
1755 'quantity': move_line.product_uos_qty or move_line.product_qty,
1756 'invoice_line_tax_id': [(6, 0, tax_ids)],
1757 'account_analytic_id': account_analytic_id,
1758- }, context=context)
1759+ }
1760+ if cv_version > 1:
1761+ inv_vals.update({'cv_line_ids': [(4, cv_line.id)],})
1762+ invoice_line_id = invoice_line_obj.create(cr, uid, inv_vals, context=context)
1763 self._invoice_line_hook(cr, uid, move_line, invoice_line_id, account_id)
1764
1765 if picking.sale_id:
1766@@ -1613,7 +1710,7 @@
1767 tax_ids = sale_line.tax_id
1768 tax_ids = map(lambda x: x.id, tax_ids)
1769
1770- account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, sale_line)
1771+ account_analytic_id = self._get_account_analytic_invoice(picking, sale_line)
1772
1773 account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, picking.sale_id.partner_id.property_account_position, account_id)
1774 invoice_line_id = invoice_line_obj.create(cr, uid, {
1775
1776=== modified file 'bin/addons/stock/stock_move.py'
1777=== modified file 'bin/addons/stock_override/stock.py'
1778--- bin/addons/stock_override/stock.py 2021-08-10 16:18:43 +0000
1779+++ bin/addons/stock_override/stock.py 2021-08-11 08:19:49 +0000
1780@@ -870,29 +870,6 @@
1781
1782 return res
1783
1784- def _get_price_unit_invoice(self, cr, uid, move_line, type):
1785- '''
1786- Update the Unit price according to the UoM received and the UoM ordered
1787- '''
1788- res = super(stock_picking, self)._get_price_unit_invoice(cr, uid, move_line, type)
1789- if type == 'in_refund':
1790- if move_line.picking_id and move_line.picking_id.purchase_id:
1791- po_line_obj = self.pool.get('purchase.order.line')
1792- po_line_id = po_line_obj.search(cr, uid, [('order_id', '=', move_line.picking_id.purchase_id.id),
1793- ('product_id', '=', move_line.product_id.id),
1794- ('state', '!=', 'cancel')
1795- ], limit=1)
1796- if po_line_id:
1797- return po_line_obj.read(cr, uid, po_line_id[0], ['price_unit'])['price_unit']
1798-
1799- if move_line.purchase_line_id:
1800- po_uom_id = move_line.purchase_line_id.product_uom.id
1801- move_uom_id = move_line.product_uom.id
1802- uom_ratio = self.pool.get('product.uom')._compute_price(cr, uid, move_uom_id, 1, po_uom_id)
1803- return res / uom_ratio
1804-
1805- return res
1806-
1807 def action_confirm(self, cr, uid, ids, context=None):
1808 """
1809 stock.picking: action confirm
1810
1811=== modified file 'bin/addons/sync_client/message.py'
1812=== modified file 'bin/addons/sync_so/picking.py'
1813=== modified file 'bin/addons/sync_so/purchase.py'
1814--- bin/addons/sync_so/purchase.py 2021-08-10 16:18:43 +0000
1815+++ bin/addons/sync_so/purchase.py 2021-08-11 08:19:49 +0000
1816@@ -332,7 +332,7 @@
1817 kind = 'update'
1818 pol_to_update = [pol_updated]
1819 confirmed_sequence = self.pool.get('purchase.order.line.state').get_sequence(cr, uid, [], 'confirmed', context=context)
1820- po_line = self.browse(cr, uid, pol_updated, fields_to_fetch=['state', 'product_qty'], context=context)
1821+ po_line = self.browse(cr, uid, pol_updated, fields_to_fetch=['state', 'product_qty', 'price_unit', 'cv_line_ids'], context=context)
1822 pol_state = po_line.state
1823 if sol_dict['state'] in ['cancel', 'cancel_r']:
1824 pol_values['cancelled_by_sync'] = True
1825@@ -340,6 +340,12 @@
1826 # if the state is less than confirmed we update the PO line
1827 if debug:
1828 logger.info("Write pol id: %s, values: %s" % (pol_to_update, pol_values))
1829+ if po_line.cv_line_ids and po_line.cv_line_ids[0] and po_line.state == 'confirmed' and po_line.product_qty - pol_values.get('product_qty', po_line.product_qty) > 0.01:
1830+ # update qty on confirmed po line: update CV line if any
1831+ # from_cancel = True : do not trigger wkf transition draft -> open
1832+ self.pool.get('account.invoice')._update_commitments_lines(cr, uid, [po_ids[0]], cvl_amount_dic={
1833+ po_line.cv_line_ids[0].id: round((po_line.product_qty - pol_values['product_qty'])*po_line.price_unit, 2)
1834+ }, from_cancel=True, context=context)
1835 self.pool.get('purchase.order.line').write(cr, uid, pol_to_update, pol_values, context=context)
1836
1837 if debug:
1838
1839=== modified file 'bin/addons/sync_so/sale.py'
1840=== modified file 'bin/addons/sync_so/so_po_common.py'
1841=== modified file 'bin/osv/expression.py'
1842--- bin/osv/expression.py 2021-08-09 18:09:28 +0000
1843+++ bin/osv/expression.py 2021-08-11 08:19:49 +0000
1844@@ -360,8 +360,13 @@
1845 if field.translate:
1846 if operator in ('like', 'ilike', 'not like', 'not ilike'):
1847 right = '%%%s%%' % right
1848+<<<<<<< TREE
1849 if right and operator in ('like', 'ilike', 'not like', 'not ilike', '=like', '=ilike'):
1850 right = right.replace('\\', '\\\\').replace('_', '\\_')
1851+=======
1852+ if operator in ('like', 'ilike', 'not like', 'not ilike', '=like', '=ilike'):
1853+ right = right.replace('\\', '\\\\').replace('_', '\\_')
1854+>>>>>>> MERGE-SOURCE
1855
1856 operator = {'=like':'like','=ilike':'ilike'}.get(operator,operator)
1857
1858@@ -486,8 +491,13 @@
1859 elif left in table._columns:
1860 params = table._columns[left]._symbol_set[1](right)
1861
1862+<<<<<<< TREE
1863 if params and operator in ('like', 'ilike', 'not like', 'not ilike', '=like', '=ilike'):
1864 params = params.replace('\\', '\\\\').replace('_', '\\_')
1865+=======
1866+ if operator in ('like', 'ilike', 'not like', 'not ilike', '=like', '=ilike'):
1867+ params = params.replace('\\', '\\\\').replace('_', '\\_')
1868+>>>>>>> MERGE-SOURCE
1869 if add_null:
1870 query = '(%s OR %s IS NULL)' % (query, left)
1871

Subscribers

People subscribed via source and target branches