[MERGE]: Merge with lp:openobject-addons
[odoo/odoo.git] / addons / auction / auction.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 from osv import fields, osv, orm
22 from tools.translate import _
23 import netsvc
24 import time
25
26 #----------------------------------------------------------
27 # Auction Artists
28 #----------------------------------------------------------
29
30 class auction_artists(osv.osv):
31     _name = "auction.artists"
32     _columns = {
33         'name': fields.char('Artist/Author Name', size=64, required=True),
34         'pseudo': fields.char('Pseudo', size=64),
35         'birth_death_dates':fields.char('Lifespan', size=64),
36         'biography': fields.text('Biography'),
37     }
38 auction_artists()
39
40 #----------------------------------------------------------
41 # Auction Dates
42 #----------------------------------------------------------
43 class auction_dates(osv.osv):
44     """Auction Dates"""
45     _name = "auction.dates"
46     _description=__doc__
47
48     def _adjudication_get(self, cr, uid, ids, prop, unknow_none, unknow_dict):
49         res={}
50         total = 0.0
51         lots_obj = self.pool.get('auction.lots')
52         for auction in self.browse(cr, uid, ids):
53             lots_ids = lots_obj.search(cr, uid, [('auction_id', '=', auction.id)])
54             for lots in lots_obj.browse(cr, uid, lots_ids):
55                 total+=lots.obj_price or 0.0
56                 res[auction.id]=total
57         return res
58
59     def name_get(self, cr, uid, ids, context=None):
60         if not context:
61             context={}
62         if not ids:
63             return []
64         reads = self.read(cr, uid, ids, ['name', 'auction1'], context)
65         name = [(r['id'], '['+r['auction1']+'] '+ r['name']) for r in reads]
66         return name
67
68     def _get_invoice(self, cr, uid, ids, name, arg, context={}):
69         lots_obj = self.pool.get('auction.lots')
70         result = {}
71         for data in self.browse(cr, uid, ids):
72             buyer_inv_ids = []
73             seller_inv_ids = []
74             result[data.id] = {
75                 'seller_invoice_history': buyer_inv_ids,
76                 'buyer_invoice_history': seller_inv_ids,
77             }
78             lots_ids = lots_obj.search(cr, uid, [('auction_id','=',data.id)])
79             for lot in lots_obj.browse(cr, uid, lots_ids):
80                 if lot.ach_inv_id:
81                     buyer_inv_ids.append(lot.ach_inv_id.id)
82                 if lot.sel_inv_id:
83                     seller_inv_ids.append(lot.sel_inv_id.id)
84             result[data.id]['seller_invoice_history'] = seller_inv_ids
85             result[data.id]['buyer_invoice_history'] = buyer_inv_ids
86         return result
87
88     _columns = {
89         'name': fields.char('Auction Name', size=64, required=True),
90         'expo1': fields.date('First Exposition Day', required=True, help="Beginning exposition date for auction"),
91         'expo2': fields.date('Last Exposition Day', required=True, help="Last exposition date for auction"),
92         'auction1': fields.date('First Auction Day', required=True, help="Start date of auction"),
93         'auction2': fields.date('Last Auction Day', required=True, help="End date of auction"),
94         'journal_id': fields.many2one('account.journal', 'Buyer Journal', required=True, help="Account journal for buyer"),
95         'journal_seller_id': fields.many2one('account.journal', 'Seller Journal', required=True, help="Account journal for seller"),
96         'buyer_costs': fields.many2many('account.tax', 'auction_buyer_taxes_rel', 'auction_id', 'tax_id', 'Buyer Costs', help="Account tax for buyer"),
97         'seller_costs': fields.many2many('account.tax', 'auction_seller_taxes_rel', 'auction_id', 'tax_id', 'Seller Costs', help="Account tax for seller"),
98         'acc_income': fields.many2one('account.account', 'Income Account', required=True),
99         'acc_expense': fields.many2one('account.account', 'Expense Account', required=True),
100         'adj_total': fields.function(_adjudication_get, method=True, string='Total Adjudication', store=True),
101         'state': fields.selection((('draft', 'Draft'), ('closed', 'Closed')), 'State', select=1, readonly=True,
102                                   help='When auction starts the state is \'Draft\'.\n At the end of auction, the state becomes \'Closed\'.'),
103         'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account', required=False),
104         'buyer_invoice_history': fields.function(_get_invoice, relation='account.invoice', method=True, string="Buyer Invoice", type='many2many', multi=True),
105         'seller_invoice_history': fields.function(_get_invoice, relation='account.invoice', method=True, string="Seller Invoice", type='many2many', multi=True),
106     }
107
108     _defaults = {
109         'state': lambda *a: 'draft',
110     }
111
112     _order = "auction1 desc"
113
114     def close(self, cr, uid, ids, context=None):
115         """
116         Close an auction date.
117
118         Create invoices for all buyers and sellers.
119         STATE ='close'
120
121         RETURN: True
122         """
123         if not context:
124             context={}
125         lots_obj = self.pool.get('auction.lots')
126         lots_ids = lots_obj.search(cr, uid, [('auction_id', 'in', ids), ('state', '=', 'draft'), ('obj_price', '>', 0)])
127         lots_obj.lots_invoice(cr, uid, lots_ids, {}, None)
128         lots_ids2 = lots_obj.search(cr, uid, [('auction_id', 'in', ids), ('obj_price', '>', 0)])
129         lots_obj.seller_trans_create(cr, uid, lots_ids2, {})
130         self.write(cr, uid, ids, {'state': 'closed'}) #close the auction
131         return True
132
133 auction_dates()
134
135 #----------------------------------------------------------
136 # Deposits
137 #----------------------------------------------------------
138 class auction_deposit(osv.osv):
139     """Auction Deposit Border"""
140
141     _name = "auction.deposit"
142     _description=__doc__
143     _order = "id desc"
144     _columns = {
145         'transfer' : fields.boolean('Transfer'),
146         'name': fields.char('Depositer Inventory', size=64, required=True),
147         'partner_id': fields.many2one('res.partner', 'Seller', required=True, change_default=True),
148         'date_dep': fields.date('Deposit date', required=True),
149         'method': fields.selection((('keep', 'Keep until sold'), ('decease', 'Decrease limit of 10%'), ('contact', 'Contact the Seller')), 'Withdrawned method', required=True),
150         'tax_id': fields.many2one('account.tax', 'Expenses'),
151         'create_uid': fields.many2one('res.users', 'Created by', readonly=True),
152         'info': fields.char('Description', size=64),
153         'lot_id': fields.one2many('auction.lots', 'bord_vnd_id', 'Objects'),
154         'specific_cost_ids': fields.one2many('auction.deposit.cost', 'deposit_id', 'Specific Costs'),
155         'total_neg': fields.boolean('Allow Negative Amount'),
156     }
157     _defaults = {
158         'method': lambda *a: 'keep',
159         'total_neg': lambda *a: False,
160         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'auction.deposit'),
161     }
162
163 auction_deposit()
164
165 #----------------------------------------------------------
166 # (Specific) Deposit Costs
167 #----------------------------------------------------------
168 class auction_deposit_cost(osv.osv):
169
170     """Auction Deposit Cost"""
171
172     _name = 'auction.deposit.cost'
173     _description=__doc__
174     _columns = {
175         'name': fields.char('Cost Name', required=True, size=64),
176         'amount': fields.float('Amount'),
177         'account': fields.many2one('account.account', 'Destination Account', required=True),
178         'deposit_id': fields.many2one('auction.deposit', 'Deposit'),
179     }
180 auction_deposit_cost()
181
182 #----------------------------------------------------------
183 # Lots Categories
184 #----------------------------------------------------------
185 class aie_category(osv.osv):
186
187     _name="aie.category"
188     _order = "name"
189     _columns={
190        'name': fields.char('Name', size=64, required=True),
191        'code':fields.char('Code', size=64),
192        'parent_id': fields.many2one('aie.category', 'Parent aie Category', ondelete='cascade'),
193        'child_ids': fields.one2many('aie.category', 'parent_id', help="Childs aie category")
194     }
195
196     def name_get(self, cr, uid, ids, context=None):
197         if not context:
198             context = {}
199         res = []
200         if not ids:
201             return res
202         reads = self.read(cr, uid, ids, ['name', 'parent_id'], context)
203         for record in reads:
204             name = record['name']
205             if record['parent_id']:
206                 name = record['parent_id'][1] + ' / ' + name
207             res.append((record['id'], name))
208         return res
209
210 aie_category()
211
212 class auction_lot_category(osv.osv):
213     """Auction Lots Category"""
214
215     _name = 'auction.lot.category'
216     _description=__doc__
217     _columns = {
218         'name': fields.char('Category Name', required=True, size=64),
219         'priority': fields.float('Priority'),
220         'active' : fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the auction lot category without removing it."),
221         'aie_categ': fields.many2one('aie.category', 'Category', ondelete='cascade'),
222     }
223     _defaults = {
224         'active' : lambda *a: 1,
225     }
226 auction_lot_category()
227
228 #----------------------------------------------------------
229 # Lots
230 #----------------------------------------------------------
231 def _type_get(self, cr, uid, context=None):
232     if not context:
233         context = {}
234     obj = self.pool.get('auction.lot.category')
235     ids = obj.search(cr, uid, [])
236     res = obj.read(cr, uid, ids, ['name'], context)
237     res = [(r['name'], r['name']) for r in res]
238     return res
239
240 class auction_lots(osv.osv):
241
242     """Auction Object"""
243     _name = "auction.lots"
244     _order = "obj_num,lot_num,id"
245     _description=__doc__
246
247     def button_not_bought(self, cr, uid, ids, context=None):
248         if not context:
249             context={}
250         return self.write(cr, uid, ids, {'state':'unsold'})
251
252     def button_taken_away(self, cr, uid, ids, context=None):
253         if not context:
254             context={}
255         return self.write(cr, uid, ids, {'state':'taken_away', 'ach_emp': True})
256
257     def button_unpaid(self, cr, uid, ids, context=None):
258         if not context:
259             context={}
260         return self.write(cr, uid, ids, {'state':'draft'})
261
262     def button_bought(self, cr, uid, ids, context=None):
263         if not context:
264             context={}
265         return self.write(cr, uid, ids, {'state':'sold'})
266
267     def _getprice(self, cr, uid, ids, fields, args, context=None):
268         """This Function compute amount total with tax for buyer and seller.
269         @param ids: List of  auction lots's id
270         @param name: List of function fields.
271         @param context: A standard dictionary for contextual values
272         @return: Dictionary of function fields value.
273         """
274         if not context:
275             context = {}
276
277         res = {}
278         account_analytic_line_obj = self.pool.get('account.analytic.line')
279         lots = self.browse(cr, uid, ids, context)
280         pt_tax = self.pool.get('account.tax')
281         for lot in lots:
282             taxes = []
283             for name in fields:
284                 res[lot.id] = {name: False}
285                 amount = lot.obj_price or 0.0
286                 result = 0.0
287                 if name == "buyer_price":
288                     if lot.author_right:
289                         taxes.append(lot.author_right)
290                     if lot.auction_id:
291                         taxes += lot.auction_id.buyer_costs
292                     tax = pt_tax.compute_all(cr, uid, taxes, amount, 1)['taxes']
293                     for t in tax:
294                         result += t.get('amount', 0.0)
295                     result += amount
296                 elif name == "seller_price":
297                     if lot.bord_vnd_id.tax_id:
298                         taxes.append(lot.bord_vnd_id.tax_id)
299                     elif lot.auction_id and lot.auction_id.seller_costs:
300                         taxes += lot.auction_id.seller_costs
301                     tax = pt_tax.compute_all(cr, uid, taxes, amount, 1)['taxes']
302                     for t in tax:
303                         result += t.get('amount', 0.0)
304                     result += amount
305                 elif name == "gross_revenue":
306                     if lot.auction_id:
307                         result = lot.buyer_price - lot.seller_price
308
309                 elif name == "net_revenue":
310                     if lot.auction_id:
311                         result = lot.buyer_price - lot.seller_price - lot.costs
312
313                 elif name == "gross_margin":
314                    if ((lot.obj_price==0) and (lot.state=='draft')):
315                      amount = lot.lot_est1
316                    else:
317                      amount = lot.obj_price
318                    if amount > 0:
319                      result = (lot.gross_revenue * 100) / amount
320                      result = round(result,2)
321
322                 elif name == "net_margin":
323                     if ((lot.obj_price==0) and (lot.state=='draft')):
324                         amount = lot.lot_est1
325                     else:
326                         amount = lot.obj_price
327                     if amount > 0:
328                         result = (lot.net_revenue * 100) / amount
329                         result = round(result,2)
330                 elif name == "costs":
331                     # costs: Total credit of analytic account
332                     # objects sold during this auction (excluding analytic lines that are in the analytic journal of the auction date)
333                     #TOCHECK: Calculation OF Indirect Cost
334                     som = 0.0
335                     if lot.auction_id:
336                         auct_id = lot.auction_id.id
337                         lot_count = self.search(cr, uid, [('auction_id', '=', auct_id)], count=True)
338                         line_ids = account_analytic_line_obj.search(cr, uid, [
339                                     ('account_id', '=', lot.auction_id.account_analytic_id.id),
340                                     ('journal_id', '<>', lot.auction_id.journal_id.id),
341                                     ('journal_id', '<>', lot.auction_id.journal_seller_id.id)])
342                         for r in lot.bord_vnd_id.specific_cost_ids:
343                             som += r.amount
344                         for line in account_analytic_line_obj.browse(cr, uid, line_ids, context=context):
345                             if line.amount:
346                                 som -= line.amount
347                         result = som/lot_count
348
349                 elif name=="paid_ach":
350                     result = False
351                     if lot.ach_inv_id and lot.ach_inv_id.state == 'paid':
352                         result = True
353
354                 elif name=="paid_vnd":
355                     result = False
356                     if lot.sel_inv_id and lot.sel_inv_id.state == 'paid':
357                         result = True
358
359                 res[lot.id][name] = result
360
361
362         return res
363
364     def onchange_obj_ret(self, cr, uid, ids, obj_ret, context=None):
365         if not context:
366             context={}
367         if obj_ret:
368             return {'value': {'obj_price': 0}}
369         return {}
370
371     _columns = {
372         'bid_lines':fields.one2many('auction.bid_line', 'lot_id', 'Bids'),
373         'auction_id': fields.many2one('auction.dates', 'Auction', select=1, help="Auction for object"),
374         'bord_vnd_id': fields.many2one('auction.deposit', 'Depositer Inventory', required=True, help="Provide deposit information: seller, Withdrawned Method, Object, Deposit Costs"),
375         'name': fields.char('Title', size=64, required=True, help='Auction object name'),
376         'name2': fields.char('Short Description (2)', size=64),
377         'lot_type': fields.selection(_type_get, 'Object category', size=64),
378         'author_right': fields.many2one('account.tax', 'Author rights', help="Account tax for author commission"),
379         'lot_est1': fields.float('Minimum Estimation', help="Minimum Estimate Price"),
380         'lot_est2': fields.float('Maximum Estimation', help="Maximum Estimate Price"),
381         'lot_num': fields.integer('List Number', required=True, select=1, help="List number in depositer inventory"),
382         'create_uid': fields.many2one('res.users', 'Created by', readonly=True),
383         'history_ids':fields.one2many('auction.lot.history', 'lot_id', 'Auction history'),
384         'lot_local':fields.char('Location', size=64, help="Auction Location"),
385         'artist_id':fields.many2one('auction.artists', 'Artist/Author'),
386         'artist2_id':fields.many2one('auction.artists', 'Artist/Author2'),
387         'important':fields.boolean('To be Emphatized'),
388         'product_id':fields.many2one('product.product', 'Product', required=True),
389         'obj_desc': fields.text('Object Description'),
390         'obj_num': fields.integer('Catalog Number'),
391         'obj_ret': fields.float('Price retired', help="Object Ret"),
392         'obj_comm': fields.boolean('Commission'),
393         'obj_price': fields.float('Adjudication price', help="Object Price"),
394         'ach_avance': fields.float('Buyer Advance'),
395         'ach_login': fields.char('Buyer Username', size=64),
396         'ach_uid': fields.many2one('res.partner', 'Buyer'),
397         'seller_id': fields.related('bord_vnd_id','partner_id', type='many2one', relation='res.partner', string='Seller', readonly=True),
398         'ach_emp': fields.boolean('Taken Away', readonly=True, help="When state is Taken Away, this field is marked as True"),
399         'is_ok': fields.boolean('Buyer\'s payment', help="When buyer pay for bank statement', this field is marked"),
400         'ach_inv_id': fields.many2one('account.invoice', 'Buyer Invoice', readonly=True, states={'draft':[('readonly', False)]}),
401         'sel_inv_id': fields.many2one('account.invoice', 'Seller Invoice', readonly=True, states={'draft':[('readonly', False)]}),
402         'vnd_lim': fields.float('Seller limit'),
403         'vnd_lim_net': fields.boolean('Net limit ?', readonly=True),
404         'image': fields.binary('Image', help="Object Image"),
405         'paid_vnd':fields.function(_getprice, string='Seller Paid', method=True, type='boolean', store=True, multi="paid_vnd", help="When state of Seller Invoice is 'Paid', this field is selected as True."),
406         'paid_ach':fields.function(_getprice, string='Buyer Invoice Reconciled', method=True, type='boolean', store=True, multi="paid_ach", help="When state of Buyer Invoice is 'Paid', this field is selected as True."),
407         'state': fields.selection((
408             ('draft', 'Draft'),
409             ('unsold', 'Unsold'),
410             ('paid', 'Paid'),
411             ('sold', 'Sold'),
412             ('taken_away', 'Taken away')), 'State', required=True, readonly=True,
413             help=' * The \'Draft\' state is used when a object is encoding as a new object. \
414                 \n* The \'Unsold\' state is used when object does not sold for long time, user can also set it as draft state after unsold. \
415                 \n* The \'Paid\' state is used when user pay for the object \
416                 \n* The \'Sold\' state is used when user buy the object.'),
417         'buyer_price': fields.function(_getprice, method=True, string='Buyer price', store=True, multi="buyer_price", help="Buyer Price"),
418         'seller_price': fields.function(_getprice, method=True, string='Seller price', store=True, multi="seller_price", help="Seller Price"),
419         'gross_revenue':fields.function(_getprice, method=True, string='Gross revenue', store=True, multi="gross_revenue", help="Buyer Price - Seller Price"),
420         'gross_margin':fields.function(_getprice, method=True, string='Gross Margin (%)', store=True, multi="gross_margin", help="(Gross Revenue*100.0)/ Object Price"),
421         'costs':fields.function(_getprice, method=True, string='Indirect costs', store=True, multi="costs", help="Deposit cost"),
422         'statement_id': fields.many2many('account.bank.statement.line', 'auction_statement_line_rel', 'auction_id', 'statement', 'Payment', help="Bank statement line for given buyer"),
423         'net_revenue':fields.function(_getprice, method=True, string='Net revenue', store=True, multi="net_revenue", help="Buyer Price - Seller Price - Indirect Cost"),
424         'net_margin':fields.function(_getprice, method=True, string='Net Margin (%)', store=True, multi="net_margin", help="(Net Revenue * 100)/ Object Price"),
425     }
426     _defaults = {
427         'state':lambda *a: 'draft',
428         'lot_num':lambda *a:1,
429         'is_ok': lambda *a: False,
430     }
431
432     def name_get(self, cr, user, ids, context=None):
433         if not context:
434             context={}
435         if not ids:
436             return []
437         result = [ (r['id'], str(r['obj_num'])+' - '+r['name']) for r in self.read(cr, user, ids, ['name', 'obj_num'])]
438         return result
439
440     def name_search(self, cr, user, name, args=None, operator='ilike', context=None):
441         if not context:
442             context={}
443         if not args:
444             args = []
445         ids = []
446         if name:
447             ids = self.search(cr, user, [('obj_num', '=', int(name))] + args)
448         if not ids:
449             ids = self.search(cr, user, [('name', operator, name)] + args)
450         return self.name_get(cr, user, ids)
451
452     def _sum_taxes_by_type_and_id(self, taxes):
453         """
454         PARAMS: taxes: a list of dictionaries of the form {'id':id, 'amount':amount, ...}
455         RETURNS : a list of dictionaries of the form {'id':id, 'amount':amount, ...}; one dictionary per unique id.
456             The others fields in the dictionaries (other than id and amount) are those of the first tax with a particular id.
457         """
458         taxes_summed = {}
459         for tax in taxes:
460             key = (tax['type'], tax['id'])
461             if key in taxes_summed:
462                 taxes_summed[key]['amount'] += tax['amount']
463             else:
464                 taxes_summed[key] = tax
465         return taxes_summed.values()
466
467     def compute_buyer_costs(self, cr, uid, ids):
468         amount_total = {}
469         lots = self.browse(cr, uid, ids)
470         ##CHECKME: Is that AC would be worthwhile to make groups of lots that have the same costs to spend a lot of lists compute?
471         taxes = []
472         amount=0.0
473         pt_tax = self.pool.get('account.tax')
474         for lot in lots:
475             taxes = lot.product_id.taxes_id
476             if lot.author_right:
477                 taxes.append(lot.author_right)
478             elif lot.auction_id:
479                 taxes += lot.auction_id.buyer_costs
480             tax=pt_tax.compute_all(cr, uid, taxes, lot.obj_price, 1)['taxes']
481             for t in tax:
482                 amount+=t['amount']
483         amount_total['value']= amount
484         amount_total['amount']= amount
485
486         return amount_total
487
488
489     def _compute_lot_seller_costs(self, cr, uid, lot, manual_only=False):
490         costs = []
491         tax_cost_ids=[]
492
493         border_id = lot.bord_vnd_id
494         if border_id:
495             if border_id.tax_id:
496                 tax_cost_ids.append(border_id.tax_id)
497             elif lot.auction_id and lot.auction_id.seller_costs:
498                 tax_cost_ids += lot.auction_id.seller_costs
499
500         tax_costs = self.pool.get('account.tax').compute_all(cr, uid, tax_cost_ids, lot.obj_price, 1)['taxes']
501         # delete useless keys from the costs computed by the tax object... this is useless but cleaner...
502         for cost in tax_costs:
503             del cost['account_paid_id']
504             del cost['account_collected_id']
505
506         if not manual_only:
507             costs.extend(tax_costs)
508             for c in costs:
509                 c.update({'type': 0})
510 ######
511         if lot.vnd_lim_net<0 and lot.obj_price>0:
512             #FIXME:the string passes have lot 'should go through the system translations.
513             obj_price_wh_costs = reduce(lambda x, y: x + y['amount'], tax_costs, lot.obj_price)
514             if obj_price_wh_costs < lot.vnd_lim:
515                 costs.append({  'type': 1,
516                                 'id': lot.obj_num,
517                                 'name': 'Remise lot '+ str(lot.obj_num),
518                                 'amount': lot.vnd_lim - obj_price_wh_costs}
519                             )
520         return costs
521
522     def compute_seller_costs(self, cr, uid, ids, manual_only=False):
523         lots = self.browse(cr, uid, ids)
524         costs = []
525
526         # group objects (lots) by deposit id
527         # ie create a dictionary containing lists of objects
528         bord_lots = {}
529         for lot in lots:
530             key = lot.bord_vnd_id.id
531             if not key in bord_lots:
532                 bord_lots[key] = []
533             bord_lots[key].append(lot)
534
535         # use each list of object in turn
536         for lots in bord_lots.values():
537             total_adj = 0
538             total_cost = 0
539             for lot in lots:
540                 total_adj += lot.obj_price or 0.0
541                 lot_costs = self._compute_lot_seller_costs(cr, uid, lot, manual_only)
542                 for c in lot_costs:
543                     total_cost += c['amount']
544                 costs.extend(lot_costs)
545             bord = lots[0].bord_vnd_id
546             if bord:
547                 if bord.specific_cost_ids:
548                     bord_costs = [{'type':2, 'id':c.id, 'name':c.name, 'amount':c.amount, 'account_id':c.account} for c in bord.specific_cost_ids]
549                     for c in bord_costs:
550                         total_cost += c['amount']
551                     costs.extend(bord_costs)
552             if (total_adj+total_cost)<0:
553                 #FIXME: translate tax name
554                 new_id = bord and bord.id or 0
555                 c = {'type':3, 'id':new_id, 'amount':-total_cost-total_adj, 'name':'Ristourne'}
556                 costs.append(c)
557         return self._sum_taxes_by_type_and_id(costs)
558
559     # sum remise limite net and ristourne
560     def compute_seller_costs_summed(self, cr, uid, ids): #ach_pay_id
561
562         """This Fuction  sum Net remittance limit and refund"""
563
564         taxes = self.compute_seller_costs(cr, uid, ids)
565         taxes_summed = {}
566         for tax in taxes:
567             if tax['type'] == 1:
568                 tax['id'] = 0
569                 #FIXME: translate tax names
570                 tax['name'] = 'Discount sharp boundary'
571             elif tax['type'] == 2:
572                 tax['id'] = 0
573                 tax['name'] = 'Miscellaneous expenditure'
574             elif tax['type'] == 3:
575                 tax['id'] = 0
576                 tax['name'] = 'Cross.'
577             key = (tax['type'], tax['id'])
578             if key in taxes_summed:
579                 taxes_summed[key]['amount'] += tax['amount']
580             else:
581                 taxes_summed[key] = tax
582         return taxes_summed.values()
583
584     def buyer_proforma(self, cr, uid, ids, context=None):
585
586         if not context:
587             context={}
588         invoices = {}
589         inv_ref = self.pool.get('account.invoice')
590         res_obj = self.pool.get('res.partner')
591         inv_line_obj = self.pool.get('account.invoice.line')
592         wf_service = netsvc.LocalService('workflow')
593         for lot in self.browse(cr, uid, ids, context):
594             if not lot.obj_price>0:
595                 continue
596             if not lot.ach_uid.id:
597                 raise orm.except_orm(_('Missed buyer !'), _('The object "%s" has no buyer assigned.') % (lot.name,))
598             else:
599                 partner_ref =lot.ach_uid.id
600                 res = res_obj.address_get(cr, uid, [partner_ref], ['contact', 'invoice'])
601                 contact_addr_id = res['contact']
602                 invoice_addr_id = res['invoice']
603                 if not invoice_addr_id:
604                     raise orm.except_orm(_('No Invoice Address'), _('The Buyer "%s" has no Invoice Address.') % (contact_addr_id,))
605                 inv = {
606                     'name': 'Auction proforma:' +lot.name,
607                     'journal_id': lot.auction_id.journal_id.id,
608                     'partner_id': partner_ref,
609                     'type': 'out_invoice',
610                 }
611                 inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', partner_ref)['value'])
612                 inv['account_id'] = inv['account_id'] and inv['account_id'][0]
613                 inv_id = inv_ref.create(cr, uid, inv, context)
614                 invoices[partner_ref] = inv_id
615                 self.write(cr, uid, [lot.id], {'ach_inv_id':inv_id, 'state':'sold'})
616
617                 #calcul des taxes
618                 taxes = map(lambda x: x.id, lot.product_id.taxes_id)
619                 taxes+=map(lambda x:x.id, lot.auction_id.buyer_costs)
620                 if lot.author_right:
621                     taxes.append(lot.author_right.id)
622
623                 inv_line= {
624                     'invoice_id': inv_id,
625                     'quantity': 1,
626                     'product_id': lot.product_id.id,
627                     'name': 'proforma'+'['+str(lot.obj_num)+'] '+ lot.name,
628                     'invoice_line_tax_id': [(6, 0, taxes)],
629                     'account_analytic_id': lot.auction_id.account_analytic_id.id,
630                     'account_id': lot.auction_id.acc_income.id,
631                     'price_unit': lot.obj_price,
632                 }
633                 inv_line_obj.create(cr, uid, inv_line, context)
634             inv_ref.button_compute(cr, uid, invoices.values())
635             wf_service.trg_validate(uid, 'account.invoice', inv_id, 'invoice_proforma2', cr)
636         return invoices.values()
637
638     # creates the transactions between the auction company and the seller
639     # this is done by creating a new in_invoice for each
640     def seller_trans_create(self, cr, uid, ids, context=None):
641         """
642             Create a seller invoice for each bord_vnd_id, for selected ids.
643         """
644         # use each list of object in turn
645         invoices = {}
646         if not context:
647             context={}
648         inv_ref=self.pool.get('account.invoice')
649         inv_line_obj = self.pool.get('account.invoice.line')
650         wf_service = netsvc.LocalService('workflow')
651         for lot in self.browse(cr, uid, ids, context):
652             if not lot.auction_id.id:
653                 continue
654             if lot.bord_vnd_id.id in invoices:
655                 inv_id = invoices[lot.bord_vnd_id.id]
656             else:
657                 inv = {
658                     'name': 'Auction:' +lot.name,
659                     'journal_id': lot.auction_id.journal_seller_id.id,
660                     'partner_id': lot.bord_vnd_id.partner_id.id,
661                     'type': 'in_invoice',
662                 }
663                 inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'in_invoice', lot.bord_vnd_id.partner_id.id)['value'])
664                 inv_id = inv_ref.create(cr, uid, inv, context)
665                 invoices[lot.bord_vnd_id.id] = inv_id
666
667             self.write(cr, uid, [lot.id], {'sel_inv_id':inv_id, 'state':'sold'})
668
669             taxes = map(lambda x: x.id, lot.product_id.taxes_id)
670             if lot.bord_vnd_id.tax_id:
671                 taxes.append(lot.bord_vnd_id.tax_id.id)
672             else:
673                 taxes += map(lambda x: x.id, lot.auction_id.seller_costs)
674
675             inv_line= {
676                 'invoice_id': inv_id,
677                 'quantity': 1,
678                 'product_id': lot.product_id.id,
679                 'name': '['+str(lot.obj_num)+'] '+lot.auction_id.name,
680                 'invoice_line_tax_id': [(6, 0, taxes)],
681                 'account_analytic_id': lot.auction_id.account_analytic_id.id,
682                 'account_id': lot.auction_id.acc_expense.id,
683                 'price_unit': lot.obj_price,
684             }
685             inv_line_obj.create(cr, uid, inv_line, context)
686             inv_ref.button_compute(cr, uid, invoices.values())
687         for inv in inv_ref.browse(cr, uid, invoices.values(), context):
688             inv_ref.write(cr, uid, [inv.id], {
689                 'check_total': inv.amount_total
690             })
691             wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_open', cr)
692         return invoices.values()
693
694     def lots_invoice(self, cr, uid, ids, context, invoice_number=False):
695         """(buyer invoice
696             Create an invoice for selected lots (IDS) to BUYER_ID.
697             Set created invoice to the ACTION state.
698             PRE:
699                 ACTION:
700                     False: no action
701                     xxxxx: set the invoice state to ACTION
702
703             RETURN: id of generated invoice
704         """
705         inv_ref = self.pool.get('account.invoice')
706         res_obj = self.pool.get('res.partner')
707         inv_line_obj = self.pool.get('account.invoice.line')
708         wf_service = netsvc.LocalService('workflow')
709         invoices={}
710         for lot in self.browse(cr, uid, ids, context):
711             if not lot.auction_id.id:
712                 continue
713             if not lot.ach_uid.id:
714                 raise orm.except_orm(_('Missed buyer !'), _('The object "%s" has no buyer assigned.') % (lot.name,))
715             if (lot.auction_id.id, lot.ach_uid.id) in invoices:
716                 inv_id = invoices[(lot.auction_id.id, lot.ach_uid.id)]
717             else:
718                 add = res_obj.read(cr, uid, [lot.ach_uid.id], ['address'])[0]['address']
719                 if not len(add):
720                     raise orm.except_orm(_('Missed Address !'), _('The Buyer has no Invoice Address.'))
721                 inv = {
722                     'name':lot.auction_id.name or '',
723                     'reference': lot.ach_login,
724                     'journal_id': lot.auction_id.journal_id.id,
725                     'partner_id': lot.ach_uid.id,
726                     'type': 'out_invoice',
727                 }
728                 if invoice_number:
729                     inv['number'] = invoice_number
730                 inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', lot.ach_uid.id)['value'])
731                 inv_id = inv_ref.create(cr, uid, inv, context)
732                 invoices[(lot.auction_id.id, lot.ach_uid.id)] = inv_id
733             self.write(cr, uid, [lot.id], {'ach_inv_id':inv_id, 'state':'sold'})
734             #calcul des taxes
735             taxes = map(lambda x: x.id, lot.product_id.taxes_id)
736             taxes+=map(lambda x:x.id, lot.auction_id.buyer_costs)
737             if lot.author_right:
738                 taxes.append(lot.author_right.id)
739
740             inv_line= {
741                 'invoice_id': inv_id,
742                 'quantity': 1,
743                 'product_id': lot.product_id.id,
744                 'name': '['+str(lot.obj_num)+'] '+ lot.name,
745                 'invoice_line_tax_id': [(6, 0, taxes)],
746                 'account_analytic_id': lot.auction_id.account_analytic_id.id,
747                 'account_id': lot.auction_id.acc_income.id,
748                 'price_unit': lot.obj_price,
749             }
750             inv_line_obj.create(cr, uid, inv_line, context)
751             inv_ref.button_compute(cr, uid, [inv_id])
752         for l in  inv_ref.browse(cr, uid, invoices.values(), context):
753             wf_service.trg_validate(uid, 'account.invoice', l.id, 'invoice_open', cr)
754         return invoices.values()
755
756 auction_lots()
757
758 #----------------------------------------------------------
759 # Auction Bids
760 #----------------------------------------------------------
761 class auction_bid(osv.osv):
762     """Bid Auctions"""
763
764     _name = "auction.bid"
765     _description=__doc__
766     _order = 'id desc'
767     _columns = {
768         'partner_id': fields.many2one('res.partner', 'Buyer Name', required=True),
769         'contact_tel':fields.char('Contact Number', size=64),
770         'name': fields.char('Bid ID', size=64, required=True),
771         'auction_id': fields.many2one('auction.dates', 'Auction Date', required=True),
772         'bid_lines': fields.one2many('auction.bid_line', 'bid_id', 'Bid'),
773     }
774     _defaults = {
775         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'auction.bid'),
776     }
777
778     def onchange_contact(self, cr, uid, ids, partner_id):
779         if not partner_id:
780             return {'value': {'contact_tel':False}}
781         contact = self.pool.get('res.partner').browse(cr, uid, partner_id)
782         if len(contact.address):
783             v_contact=contact.address[0] and contact.address[0].phone
784         else:
785             v_contact = False
786         return {'value': {'contact_tel': v_contact}}
787
788 auction_bid()
789
790 class auction_lot_history(osv.osv):
791     """Lot History"""
792
793     _name = "auction.lot.history"
794     _description=__doc__
795     _columns = {
796         'name': fields.date('Date', size=64),
797         'lot_id': fields.many2one('auction.lots', 'Object', required=True, ondelete='cascade'),
798         'auction_id': fields.many2one('auction.dates', 'Auction date', required=True, ondelete='cascade'),
799         'price': fields.float('Withdrawn price', digits=(16, 2))
800     }
801     _defaults = {
802         'name': lambda *args: time.strftime('%Y-%m-%d')
803     }
804 auction_lot_history()
805
806 class auction_bid_lines(osv.osv):
807     _name = "auction.bid_line"
808     _description="Bid"
809
810     _columns = {
811         'name': fields.char('Bid date', size=64),
812         'bid_id': fields.many2one('auction.bid', 'Bid ID', required=True, ondelete='cascade'),
813         'lot_id': fields.many2one('auction.lots', 'Object', required=True, ondelete='cascade'),
814         'call': fields.boolean('To be Called'),
815         'price': fields.float('Maximum Price'),
816         'auction': fields.char(string='Auction Name', size=64)
817     }
818     _defaults = {
819         'name': lambda *args: time.strftime('%Y-%m-%d')
820     }
821
822     def onchange_name(self, cr, uid, ids, lot_id):
823         if not lot_id:
824             return {'value': {'auction':False}}
825         auctions = self.pool.get('auction.lots').browse(cr, uid, lot_id)
826         v_auction=auctions.auction_id.name or False
827         return {'value': {'auction': v_auction}}
828
829
830 auction_bid_lines()
831
832 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: