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