1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 ##############################################################################
22 from operator import itemgetter
25 from openerp.osv import fields, osv
27 class account_fiscal_position(osv.osv):
28 _name = 'account.fiscal.position'
29 _description = 'Fiscal Position'
31 'name': fields.char('Fiscal Position', size=64, required=True),
32 'active': fields.boolean('Active', help="By unchecking the active field, you may hide a fiscal position without deleting it."),
33 'company_id': fields.many2one('res.company', 'Company'),
34 'account_ids': fields.one2many('account.fiscal.position.account', 'position_id', 'Account Mapping'),
35 'tax_ids': fields.one2many('account.fiscal.position.tax', 'position_id', 'Tax Mapping'),
36 'note': fields.text('Notes', translate=True),
43 def map_tax(self, cr, uid, fposition_id, taxes, context=None):
47 return map(lambda x: x.id, taxes)
51 for tax in fposition_id.tax_ids:
52 if tax.tax_src_id.id == t.id:
54 result.add(tax.tax_dest_id.id)
60 def map_account(self, cr, uid, fposition_id, account_id, context=None):
63 for pos in fposition_id.account_ids:
64 if pos.account_src_id.id == account_id:
65 account_id = pos.account_dest_id.id
69 account_fiscal_position()
71 class account_fiscal_position_tax(osv.osv):
72 _name = 'account.fiscal.position.tax'
73 _description = 'Taxes Fiscal Position'
74 _rec_name = 'position_id'
76 'position_id': fields.many2one('account.fiscal.position', 'Fiscal Position', required=True, ondelete='cascade'),
77 'tax_src_id': fields.many2one('account.tax', 'Tax Source', required=True),
78 'tax_dest_id': fields.many2one('account.tax', 'Replacement Tax')
83 'unique (position_id,tax_src_id,tax_dest_id)',
84 'A tax fiscal position could be defined only once time on same taxes.')
87 account_fiscal_position_tax()
89 class account_fiscal_position_account(osv.osv):
90 _name = 'account.fiscal.position.account'
91 _description = 'Accounts Fiscal Position'
92 _rec_name = 'position_id'
94 'position_id': fields.many2one('account.fiscal.position', 'Fiscal Position', required=True, ondelete='cascade'),
95 'account_src_id': fields.many2one('account.account', 'Account Source', domain=[('type','<>','view')], required=True),
96 'account_dest_id': fields.many2one('account.account', 'Account Destination', domain=[('type','<>','view')], required=True)
100 ('account_src_dest_uniq',
101 'unique (position_id,account_src_id,account_dest_id)',
102 'An account fiscal position could be defined only once time on same accounts.')
105 account_fiscal_position_account()
107 class res_partner(osv.osv):
108 _name = 'res.partner'
109 _inherit = 'res.partner'
110 _description = 'Partner'
112 def _credit_debit_get(self, cr, uid, ids, field_names, arg, context=None):
113 query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
114 cr.execute("""SELECT l.partner_id, a.type, SUM(l.debit-l.credit)
115 FROM account_move_line l
116 LEFT JOIN account_account a ON (l.account_id=a.id)
117 WHERE a.type IN ('receivable','payable')
118 AND l.partner_id IN %s
119 AND l.reconcile_id IS NULL
120 AND """ + query + """
121 GROUP BY l.partner_id, a.type
124 maps = {'receivable':'credit', 'payable':'debit' }
127 res[id] = {}.fromkeys(field_names, 0)
128 for pid,type,val in cr.fetchall():
129 if val is None: val=0
130 res[pid][maps[type]] = (type=='receivable') and val or -val
133 def _asset_difference_search(self, cr, uid, obj, name, type, args, context=None):
136 having_values = tuple(map(itemgetter(2), args))
137 where = ' AND '.join(
138 map(lambda x: '(SUM(bal2) %(operator)s %%s)' % {
139 'operator':x[1]},args))
140 query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
141 cr.execute(('SELECT pid AS partner_id, SUM(bal2) FROM ' \
142 '(SELECT CASE WHEN bal IS NOT NULL THEN bal ' \
143 'ELSE 0.0 END AS bal2, p.id as pid FROM ' \
144 '(SELECT (debit-credit) AS bal, partner_id ' \
145 'FROM account_move_line l ' \
146 'WHERE account_id IN ' \
147 '(SELECT id FROM account_account '\
148 'WHERE type=%s AND active) ' \
149 'AND reconcile_id IS NULL ' \
150 'AND '+query+') AS l ' \
151 'RIGHT JOIN res_partner p ' \
152 'ON p.id = partner_id ) AS pl ' \
153 'GROUP BY pid HAVING ' + where),
154 (type,) + having_values)
157 return [('id','=','0')]
158 return [('id','in',map(itemgetter(0), res))]
160 def _credit_search(self, cr, uid, obj, name, args, context=None):
161 return self._asset_difference_search(cr, uid, obj, name, 'receivable', args, context=context)
163 def _debit_search(self, cr, uid, obj, name, args, context=None):
164 return self._asset_difference_search(cr, uid, obj, name, 'payable', args, context=context)
166 def has_something_to_reconcile(self, cr, uid, partner_id, context=None):
168 at least a debit, a credit and a line older than the last reconciliation date of the partner
171 SELECT l.partner_id, SUM(l.debit) AS debit, SUM(l.credit) AS credit
172 FROM account_move_line l
173 RIGHT JOIN account_account a ON (a.id = l.account_id)
174 RIGHT JOIN res_partner p ON (l.partner_id = p.id)
175 WHERE a.reconcile IS TRUE
177 AND l.reconcile_id IS NULL
178 AND (p.last_reconciliation_date IS NULL OR l.date > p.last_reconciliation_date)
179 AND l.state <> 'draft'
180 GROUP BY l.partner_id''', (partner_id,))
181 res = cr.dictfetchone()
183 return bool(res['debit'] and res['credit'])
186 def mark_as_reconciled(self, cr, uid, ids, context=None):
187 return self.write(cr, uid, ids, {'last_reconciliation_date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
190 'credit': fields.function(_credit_debit_get,
191 fnct_search=_credit_search, string='Total Receivable', multi='dc', help="Total amount this customer owes you."),
192 'debit': fields.function(_credit_debit_get, fnct_search=_debit_search, string='Total Payable', multi='dc', help="Total amount you have to pay to this supplier."),
193 'debit_limit': fields.float('Payable Limit'),
194 'property_account_payable': fields.property(
197 relation='account.account',
198 string="Account Payable",
200 domain="[('type', '=', 'payable')]",
201 help="This account will be used instead of the default one as the payable account for the current partner",
203 'property_account_receivable': fields.property(
206 relation='account.account',
207 string="Account Receivable",
209 domain="[('type', '=', 'receivable')]",
210 help="This account will be used instead of the default one as the receivable account for the current partner",
212 'property_account_position': fields.property(
213 'account.fiscal.position',
215 relation='account.fiscal.position',
216 string="Fiscal Position",
218 help="The fiscal position will determine taxes and accounts used for the partner.",
220 'property_payment_term': fields.property(
221 'account.payment.term',
223 relation='account.payment.term',
224 string ='Customer Payment Terms',
226 help="This payment term will be used instead of the default one for sale orders and customer invoices"),
227 'property_supplier_payment_term': fields.property(
228 'account.payment.term',
230 relation='account.payment.term',
231 string ='Supplier Payment Terms',
233 help="This payment term will be used instead of the default one for purchase orders and supplier invoices"),
234 'ref_companies': fields.one2many('res.company', 'partner_id',
235 'Companies that refers to partner'),
236 'last_reconciliation_date': fields.datetime('Latest Reconciliation Date', help='Date on which the partner accounting entries were fully reconciled last time. It differs from the date of the last reconciliation made for this partner, as here we depict the fact that nothing more was to be reconciled at this date. This can be achieved in 2 ways: either the last debit/credit entry was reconciled, either the user pressed the button "Fully Reconciled" in the manual reconciliation process')
241 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: