1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 MSF, TeMPO Consulting.
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
26
from osv import fields
27
from tools.translate import _
29
REAL_MODEL_LIST = [('sale.order', 'Sale Order'),
30
('purchase.order', 'Purchase Order'),
31
('internal.request', 'Internal Request'),
32
('rfq', 'Request for Quotation'),
36
class documents_done_wizard(osv.osv):
37
_name = 'documents.done.wizard'
38
_description = 'Documents not \'Done\''
41
def _get_selection(self, cr, uid, context=None):
46
for model in context.get('models', ['sale.order', 'purchase.order', 'tender']):
47
sel = self.pool.get(model).fields_get(cr, uid, ['state'])
48
res = sel['state']['selection']
50
if not 'db_value' in context:
51
if (st[1], st[1]) not in states and st[0] not in ('done', 'cancel'):
52
states.append((st[1], st[1]))
54
if (st[0], st[1]) not in states and st[0] not in ('done', 'cancel'):
55
states.append((st[0], st[1]))
60
def _get_model_from_state(self, cr, uid, state, context=None):
62
Returns the model which have the value of state in the selection field 'state'.
70
for model in context.get('models', ['sale.order', 'purchase.order', 'tender']):
71
sel = self.pool.get(model).fields_get(cr, uid, ['state'])
72
for st in sel['state']['selection']:
79
def _get_state(self, cr, uid, ids, field_name, args, context=None):
81
Returns the good value according to the doc type
87
for doc in self.browse(cr, uid, ids, context=context):
88
context.update({'models': [doc.real_model], 'db_value': True})
89
for state in self._get_selection(cr, uid, context=context):
90
if state[0] == doc.state:
91
res[doc.id] = state[1]
95
def _search_state(self, cr, uid, obj, name, args, context=None):
97
Returns all documents according to state
102
if arg[0] == 'display_state':
103
docs, db_values = self._get_model_from_state(cr, uid, arg[2])
104
ids = self.pool.get('documents.done.wizard').search(cr, uid, [('real_model', 'in', docs), ('state', 'in', db_values)], context=context)
106
return [('id', 'in', ids)]
108
def _get_related_stock_moves(self, cr, uid, order, field, context=None):
110
Returns all stock moves related to an order (sale.order/purchase.order)
113
for line in order.order_line:
114
line_ids.append(line.id)
116
if order._name == 'sale.order' and order.procurement_request:
117
return self.pool.get('stock.move').search(cr, uid, [('state', 'not in', ['cancel', 'done']), (field, 'in', line_ids)], context=context)
118
return self.pool.get('stock.move').search(cr, uid, [('state', 'not in', ['cancel', 'done']), (field, 'in', line_ids), ('type', '!=', 'internal')], context=context)
120
def _get_problem_sale_order(self, cr, uid, order, context=None):
122
Check if all stock moves, all procurement orders, all purchase orders
123
and all stock picking generated from the sale order is closed or canceled
127
move_ids = self._get_related_stock_moves(cr, uid, order, 'sale_line_id', context=context)
133
for line in order.order_line:
134
# Check procurement orders
135
if line.procurement_id:
136
if line.procurement_id.state not in ('cancel', 'done'):
137
proc_ids.append(line.procurement_id.id)
139
if line.procurement_id.purchase_id and line.procurement_id.purchase_id.state not in ('cancel', 'done'):
140
po_ids.append(line.procurement_id.purchase_id.id)
142
if line.procurement_id.tender_id and line.procurement_id.tender_id.state not in ('cancel', 'done'):
143
tender_ids.append(line.procurement_id.tender_id.id)
145
for rfq in line.procurement_id.tender_id.rfq_ids:
146
if rfq.state not in ('cancel', 'done'):
147
po_ids.append(rfq.id)
149
# Check loan counterpart
150
if order.loan_id and order.loan_id.state not in ('cancel', 'done'):
151
po_ids.append(order.loan_id.id)
154
#for invoice in order.invoice_ids:
155
# if invoice.state not in ('cancel', 'paid'):
156
# invoice_ids.append(invoice.id)
158
if context.get('count', False):
159
return move_ids or proc_ids or po_ids or tender_ids or invoice_ids or False
161
return move_ids, proc_ids, po_ids, tender_ids, invoice_ids
163
def _get_problem_purchase_order(self, cr, uid, order, context=None):
165
Check if all stock moves, all invoices
166
and all stock picking generated from the purchase order is closed or canceled
170
move_ids = self._get_related_stock_moves(cr, uid, order, 'purchase_line_id', context=context)
173
if order.loan_id and order.loan_id.state not in ('cancel', 'done'):
174
so_ids.append(order.loan_id.id)
177
#for invoice in order.invoice_ids:
178
# if invoice.state not in ('cancel', 'paid'):
179
# invoice_ids.append(invoice.id)
181
if context.get('count', False):
182
return move_ids or so_ids or invoice_ids or False
184
return move_ids, so_ids, invoice_ids
186
def _get_problem_tender(self, cr, uid, order, context=None):
188
Check if all request for quotations and all purchase orders
189
generated from the tender is closed or canceled
193
po_ids = self.pool.get('purchase.order').search(cr, uid, [('state', 'not in', ['cancel', 'done']), ('tender_id', '=', order.id)], context=context)
194
if context.get('count', False):
195
return po_ids or False
199
def _get_problem(self, cr, uid, ids, field_name, args, context=None):
201
Returns True if at least one doc stop the manually done processe
207
c.update({'count': True})
208
for doc in self.browse(cr, uid, ids, context=context):
209
order = self.pool.get(doc.real_model).browse(cr, uid, doc.res_id, context=context)
210
if doc.real_model == 'sale.order':
211
res[doc.id] = self._get_problem_sale_order(cr, uid, order, context=c) and True or False
212
elif doc.real_model == 'purchase.order':
213
res[doc.id] = self._get_problem_purchase_order(cr, uid, order, context=c) and True or False
214
elif doc.real_model == 'tender':
215
res[doc.id] = self._get_problem_tender(cr, uid, order, context=c) and True or False
222
'name': fields.char(size=256, string='Name', readonly=True),
223
'res_id': fields.integer(string='Res. Id'),
224
'real_model': fields.char(size=64, string='Real model'),
225
'model': fields.selection(REAL_MODEL_LIST, string='Doc. Type', readonly=True),
226
'creation_date': fields.date(string='Creation date', readonly=True),
227
'expected_date': fields.date(string='Expected date', readonly=True),
228
'partner_id': fields.many2one('res.partner', string='Partner', readonly=True),
229
'problem': fields.function(_get_problem, string='Problem', required=True, method=True, store=False,
230
type='boolean', readonly=True),
231
'state': fields.char(size=64, string='State', readonly=True),
232
'display_state': fields.function(_get_state, fnct_search=_search_state, type='selection', selection=_get_selection,
233
method=True, store=False, readonly=True, string='State'),
234
'requestor': fields.many2one('res.users', string='Creator', readonly=True),
237
def _get_model_name(self, model):
239
Returns the readable model name
241
for model_name in REAL_MODEL_LIST:
242
if model_name[0] == model:
247
def _add_stock_move_pb(self, cr, uid, problem_id, moves, context=None):
249
Add a line for each moves
253
line_obj = self.pool.get('documents.done.problem.line')
255
for move in self.pool.get('stock.move').browse(cr, uid, moves, context=context):
256
if move.picking_id and (move.type != 'out' or move.picking_id.converted_to_standard or move.picking_id.subtype == 'picking') and move.picking_id.id not in picking_ids:
257
picking_ids.append(move.picking_id.id)
258
doc_type = 'Internal move'
259
if move.type == 'out':
260
doc_type = 'Delivery Order'
261
elif move.type == 'in':
262
doc_type = 'Incoming Shipment'
263
line_obj.create(cr, uid, {'problem_id': problem_id,
264
'doc_name': move.picking_id.name,
265
'doc_state': move.picking_id.state,
266
'doc_model': 'stock.picking',
267
'doc_id': move.picking_id.id,
268
'doc_type': doc_type}, context=context)
269
elif not move.picking_id:
270
line_obj.create(cr, uid, {'problem_id': problem_id,
271
'doc_name': move.name,
272
'doc_state': move.state,
273
'doc_model': 'stock.move',
275
'doc_type': 'Stock move'}, context=context)
278
def _add_purchase_order(self, cr, uid, problem_id, po_ids, context=None):
280
Add line for each PO/RfQ
284
line_obj = self.pool.get('documents.done.problem.line')
285
for order in self.pool.get('purchase.order').browse(cr, uid, po_ids, context=context):
286
line_obj.create(cr, uid, {'problem_id': problem_id,
287
'doc_name': order.name,
288
'doc_state': order.state,
289
'doc_model': 'purchase.order',
291
'doc_type': order.rfq_ok and 'Request for Quotation' or 'Purchase Order'}, context=context)
294
def go_to_problems(self, cr, uid, ids, context=None):
296
Returns a wizard with all documents posing a problem
300
pb_obj = self.pool.get('documents.done.problem')
301
pb_line_obj = self.pool.get('documents.done.problem.line')
302
move_obj = self.pool.get('stock.move')
303
proc_obj = self.pool.get('procurement.order')
305
for wiz in self.browse(cr, uid, ids, context=context):
308
context.update({'direct_cancel': True})
309
return self.cancel_line(cr, uid, [wiz.id], all_doc=True, context=context)
319
doc = self.pool.get(wiz.real_model).browse(cr, uid, wiz.res_id, context=context)
320
pb_id = pb_obj.create(cr, uid, {'wizard_id': wiz.id,
321
'doc_name': doc.name}, context=context)
323
# For sales orders and procurement request
324
if wiz.real_model == 'sale.order':
325
order = self.pool.get('sale.order').browse(cr, uid, wiz.res_id, context=context)
326
move_ids, proc_ids, po_ids, tender_ids, invoice_ids = self._get_problem_sale_order(cr, uid, order, context=context)
327
elif wiz.real_model == 'purchase.order':
328
order = self.pool.get('purchase.order').browse(cr, uid, wiz.res_id, context=context)
329
move_ids, so_ids, invoice_ids = self._get_problem_purchase_order(cr, uid, order, context=context)
330
elif wiz.real_model == 'tender':
331
order = self.pool.get('tender').browse(cr, uid, wiz.res_id, context=context)
332
po_ids = self._get_problem_tender(cr, uid, order, context=context)
333
# Process all stock moves
334
self._add_stock_move_pb(cr, uid, pb_id, move_ids, context=context)
336
self._add_purchase_order(cr, uid, pb_id, po_ids, context=context)
337
# Process all tenders
338
for tender in self.pool.get('tender').browse(cr, uid, tender_ids, context=context):
339
pb_line_obj.create(cr, uid, {'problem_id': pb_id,
340
'doc_name': tender.name,
341
'doc_state': tender.state,
342
'doc_model': 'tender',
344
'doc_type': 'Tender'}, context=context)
345
# Search all procurement orders attached to the sale order
346
for proc in self.pool.get('procurement.order').browse(cr, uid, proc_ids, context=context):
347
pb_line_obj.create(cr, uid, {'problem_id': pb_id,
348
'doc_name': proc.name,
349
'doc_state': proc.state,
350
'doc_model': 'procurement.order',
352
'doc_type': 'Procurement Order'}, context=context)
354
# Process all invoices
355
for inv in self.pool.get('account.invoice').browse(cr, uid, invoice_ids, context=context):
356
pb_line_obj.create(cr, uid, {'problem_id': pb_id,
357
'doc_name': inv.number or inv.name,
358
'doc_state': inv.state,
359
'doc_model': 'account.invoice',
361
'doc_type': 'Invoice'}, context=context)
363
return {'type': 'ir.actions.act_window',
364
'res_model': 'documents.done.problem',
372
def cancel_line(self, cr, uid, ids, all_doc=True, context=None):
374
Set the document to done state
379
for doc in self.browse(cr, uid, ids, context=context):
380
if self.pool.get(doc.real_model).browse(cr, uid, doc.res_id, context=context).state not in ('cancel', 'done'):
381
self.pool.get(doc.real_model).set_manually_done(cr, uid, doc.res_id, all_doc=all_doc, context=context)
383
self.pool.get(doc.real_model).log(cr, uid, doc.res_id, _('The %s \'%s\' has been closed.')%(self._get_model_name(doc.real_model), doc.name), context=context)
385
if not context.get('direct_cancel', False):
386
return {'type': 'ir.actions.act_window_close'}
394
tools.drop_view_if_exists(cr, 'documents_done_wizard')
395
cr.execute("""CREATE OR REPLACE VIEW documents_done_wizard AS (
397
row_number() OVER(ORDER BY name) AS id,
411
'sale.order' AS real_model,
412
'sale.order' AS model,
414
so.date_order AS creation_date,
415
so.delivery_requested_date AS expected_date,
417
so.create_uid AS requestor
421
state NOT IN ('draft', 'done', 'cancel')
423
procurement_request = False)
428
'sale.order' AS real_model,
429
'internal.request' AS model,
431
ir.date_order AS creation_date,
432
ir.delivery_requested_date AS expected_date,
434
ir.create_uid AS requestor
438
state NOT IN ('draft', 'done', 'cancel')
440
procurement_request = True)
445
'purchase.order' AS real_model,
446
'purchase.order' AS model,
448
po.date_order AS creation_date,
449
po.delivery_requested_date AS expected_date,
450
po.partner_id AS partner_id,
451
po.create_uid AS requestor
455
state NOT IN ('draft', 'done', 'cancel')
462
'purchase.order' AS real_model,
465
rfq.date_order AS creation_date,
466
rfq.delivery_requested_date AS expected_date,
467
rfq.partner_id AS partner_id,
468
rfq.create_uid AS requestor
472
state NOT IN ('draft', 'done', 'cancel')
479
'tender' AS real_model,
482
t.creation_date AS creation_date,
483
t.requested_date AS expected_date,
485
t.create_uid AS requestor
489
state NOT IN ('draft', 'done', 'cancel'))) AS dnd
492
documents_done_wizard()
494
class documents_done_problem(osv.osv_memory):
495
_name = 'documents.done.problem'
497
def _get_errors(self, cr, uid, ids, field_name, args, context=None):
499
Returns True if at least one problem is found
505
for doc in self.browse(cr, uid, ids, context=context):
513
'wizard_id': fields.many2one('documents.done.wizard', string='Wizard'),
514
'doc_name': fields.char(size=64, string='Document'),
515
'errors': fields.function(_get_errors, method=True, store=False, string='Errors', type='boolean', readonly=True),
516
'pb_lines': fields.one2many('documents.done.problem.line', 'problem_id', string='Lines'),
519
def done_all_documents(self, cr, uid, ids, all_doc=True, context=None):
521
For all documents, check the state of the doc and send the signal
522
of 'manually_done' if needed
526
wf_service = netsvc.LocalService("workflow")
527
for wiz in self.browse(cr, uid, ids, context=context):
528
for line in wiz.pb_lines:
529
if line.doc_model == 'account.invoice':
530
invoice_state = self.pool.get('account.invoice').browse(cr, uid, line.doc_id, context=context).state
531
if invoice_state == 'draft':
532
wf_service.trg_validate(uid, line.doc_model, line.doc_id, 'invoice_cancel', cr)
533
# elif invoice_state not in ('cancel', 'paid'):
534
# raise osv.except_osv(_('Error'), _('You cannot set the SO to \'Closed\' because the following invoices are not Cancelled or Paid : %s') % ([map(x.name + '/') for x in error_inv_ids]))
535
elif line.doc_model == 'tender':
536
wf_service.trg_validate(uid, line.doc_model, line.doc_id, 'manually_done', cr)
537
elif self.pool.get(line.doc_model).browse(cr, uid, line.doc_id, context=context).state not in ('cancel', 'done'):
538
self.pool.get(line.doc_model).set_manually_done(cr, uid, line.doc_id, all_doc=all_doc, context=context)
540
return self.pool.get('documents.done.wizard').go_to_problems(cr, uid, [wiz.wizard_id.id], context=context)
542
return {'type': 'ir.actions.act_window',
543
'res_model': 'documents.done.wizard',
549
def cancel_document(self, cr, uid, ids, context=None):
555
for wiz in self.browse(cr, uid, ids, context=context):
556
return self.pool.get('documents.done.wizard').cancel_line(cr, uid, [wiz.wizard_id.id], all_doc=True, context=context)
560
documents_done_problem()
562
class documents_done_problem_line(osv.osv_memory):
563
_name = 'documents.done.problem.line'
565
def _get_state(self, cr, uid, ids, field_name, arg, context=None):
567
Return the state of the related doc
572
if isinstance(ids, (int, long)):
577
for line in self.browse(cr, uid, ids, context=context):
578
sel = self.pool.get(line.doc_model).fields_get(cr, uid, ['state'])
579
res_state = dict(sel['state']['selection']).get(line.doc_state, line.doc_state)
580
name = '%s,state' % line.doc_model
581
tr_ids = self.pool.get('ir.translation').search(cr, uid, [('type', '=', 'selection'), ('name', '=', name),('src', '=', res_state)])
582
if tr_ids and self.pool.get('ir.translation').read(cr, uid, tr_ids, ['value'])[0]['value']:
583
res[line.id] = self.pool.get('ir.translation').read(cr, uid, tr_ids, ['value'])[0]['value']
585
res[line.id] = res_state
590
'problem_id': fields.many2one('documents.done.problem', string='Problem'),
591
'doc_name': fields.char(size='64', string='Reference'),
592
'doc_state': fields.char(size=64, string='DB state'),
593
'doc_state_str': fields.function(_get_state, method=True, string='State', type='char', readonly=True),
594
'doc_type': fields.char(size=64, string='Doc. Type'),
595
'doc_model': fields.char(size=64, string='Doc. Model'),
596
'doc_id': fields.integer(string='Doc. Id'),
599
def go_to_doc(self, cr, uid, ids, context=None):
601
Open the form of the related doc
605
for item in self.browse(cr, uid, ids, context=context):
606
return {'type': 'ir.actions.act_window',
607
'res_model': item.doc_model,
608
'name': item.doc_type,
612
'res_id': item.doc_id,}
614
documents_done_problem_line()
616
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: