# -*- encoding: utf-8 -*- ############################################################################## # # Copyright (c) 2004-2005 TINY SPRL. (http://tiny.be) All Rights Reserved. # # $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $ # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## import time import netsvc from osv import fields, osv import ir import mx.DateTime from mx.DateTime import RelativeDateTime, now, DateTime, localtime class account_payment_term(osv.osv): _name = "account.payment.term" _description = "Payment Term" _columns = { 'name': fields.char('Payment Term', size=32), 'active': fields.boolean('Active'), 'note': fields.text('Description'), 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms') } _defaults = { 'active': lambda *a: 1, } _order = "name" def compute(self, cr, uid, id, value, date_ref=False, context={}): if not date_ref: date_ref = now().strftime('%Y-%m-%d') pt = self.browse(cr, uid, id, context) amount = value result = [] for line in pt.line_ids: if line.value=='fixed': amt = line.value_amount elif line.value=='procent': amt = round(amount * line.value_amount,2) elif line.value=='balance': amt = amount if amt: next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days) if line.condition == 'end of month': next_date += RelativeDateTime(day=-1) result.append( (next_date.strftime('%Y-%m-%d'), amt) ) amount -= amt return result account_payment_term() class account_uos(osv.osv): _name = 'account.uos' _descripiton = 'Product Unit of Sale' _columns = { 'name' : fields.char('Name', size=64, required=True), } account_uos() class account_payment_term_line(osv.osv): _name = "account.payment.term.line" _description = "Payment Term Line" _columns = { 'name': fields.char('Line Name', size=32,required=True), 'sequence': fields.integer('Sequence', required=True), 'value': fields.selection([('procent','Procent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True), 'value_amount': fields.float('Value Amount'), 'days': fields.integer('Number of Days',required=True), 'condition': fields.selection([('net days','Net Days'),('end of month','End of Month')], 'Condition', required=True), 'payment_id': fields.many2one('account.payment.term','Payment Term', required=True), } _defaults = { 'value': lambda *a: 'balance', 'sequence': lambda *a: 5, 'condition': lambda *a: 'net days', } _order = "sequence" account_payment_term_line() #---------------------------------------------------------- # Currency #---------------------------------------------------------- class account_currency(osv.osv): _name = "account.currency" _description = "Currency" _columns = { 'name': fields.char('Currency', size=32), 'code': fields.char('Code', size=3), 'date': fields.date('Last rate update', readonly=True), 'rate' : fields.float('Relative Change rate',digits=(12,6)), 'digits' : fields.integer('Displayed Digits'), 'accuracy' : fields.integer('Computational Accuracy'), 'active': fields.boolean('Active'), } _defaults = { 'date': lambda *a: time.strftime('%Y-%m-%d'), 'active': lambda *a: 1, } _order = "code" account_currency() class account_account_type(osv.osv): _name = "account.account.type" _description = "Account Type" _columns = { 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True), 'code': fields.char('Code', size=32, required=True), 'sequence': fields.integer('Sequence'), 'code_from': fields.date('Code From', size=8), 'code_to': fields.date('Code To', size=8), 'partner_account': fields.boolean('Partner account'), 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True), } _defaults = { 'close_method': lambda *a: 'none', 'sequence': lambda *a: 5, } _order = "sequence" account_account_type() def _code_get(self, cr, user): cr.execute('select code, name from account_account_type order by name') return cr.fetchall() #---------------------------------------------------------- # Accounts #---------------------------------------------------------- class account_account(osv.osv): _order = "code" _name = "account.account" _description = "Account" def _credit(self, cr, uid, ids, prop, unknow_none,context={}): ids = self.search(cr,uid,[('parent_id','child_of',ids)]) acc_set=",".join( map(str, ids ) ) rate={} view={} cr.execute("SELECT a.id,a.type,c.rate FROM account_account a LEFT JOIN account_currency c ON (a.currency_id=c.id) WHERE a.id IN (%s)"%acc_set) for (id,t,r) in cr.fetchall(): rate[id]=r if t=='view': view[id]=0 self.pool.get('account.account').search(cr, uid, [('parent_id', 'child_of', [id])]) cr.execute("SELECT a.id, COALESCE(SUM(l.credit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) LEFT JOIN account_move m ON (m.id=l.move_id) WHERE a.type!='view' AND a.id IN (%s) GROUP BY a.id"%acc_set) res2=cr.fetchall() res= {} for id in ids: res[id]=0.0 for account_id,sum in res2: res[account_id] += sum return res def _debit(self, cr, uid, ids, prop, unknow_none,context, where=''): ids = self.search(cr,uid,[('parent_id','child_of',ids)]) acc_set=",".join( map(str, ids ) ) rate={} view={} cr.execute("SELECT a.id,a.type,c.rate FROM account_account a LEFT JOIN account_currency c ON (a.currency_id=c.id) WHERE a.id IN (%s)"%acc_set) for (id,t,r) in cr.fetchall(): rate[id]=r if t=='view': view[id]=0 cr.execute("SELECT a.id, COALESCE(SUM(l.debit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.accounT_ID) LEFT JOIN account_move m ON (m.id=l.move_id) WHERE a.type!='view' AND a.id IN (%s) GROUP BY a.id"%acc_set) res2=cr.fetchall() res= {} for id in ids: res[id]=0.0 for account_id,sum in res2: res[account_id] += sum return res def _balance_rec(self,cr,id,rate,res): if not res.has_key(id): bal=0.0 cr.execute("SELECT child_id FROM account_account_rel WHERE parent_id=%d"%id) for (child_id,) in cr.fetchall(): bal+=self._balance_rec(cr,child_id,rate,res)*(rate[id]/rate[child_id]) res[id]=round(bal,2) return round(res[id],2) def _balance(self, cr, uid, ids, prop, unknow_none,context): ids = self.search(cr,uid,[('parent_id','child_of',ids)]) acc_set=",".join( map(str, ids ) ) rate={} view={} cr.execute("SELECT a.id,a.type,c.rate FROM account_account a LEFT JOIN account_currency c ON (a.currency_id=c.id) WHERE a.id IN (%s)"%acc_set) for (id,t,r) in cr.fetchall(): rate[id]=r if t=='view': view[id]=0 cr.execute("SELECT a.id, COALESCE(SUM((l.debit-l.credit)*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) LEFT JOIN account_move m On (m.id=l.move_id) WHERE a.type!='view' AND a.id IN (%s) GROUP BY a.id"%acc_set) res2=cr.fetchall() res= {} for id in ids: res[id]=0.0 for account_id,sum in res2: res[account_id] += sum for id in view: self._balance_rec(cr,id,rate,res) return res _columns = { 'name': fields.char('Name', size=128, required=True, translate=True), 'sign': fields.integer('Sign', required=True), 'currency_id': fields.many2one('account.currency', 'Currency', required=True), 'code': fields.char('Code', size=64 ), 'type': fields.selection(_code_get, 'Account Type', required=True), 'parent_id': fields.many2many('account.account', 'account_account_rel', 'child_id', 'parent_id', 'Parents'), 'child_id': fields.many2many('account.account', 'account_account_rel', 'parent_id', 'child_id', 'Children'), 'balance': fields.function(_balance, method=True, string='Balance'), 'credit': fields.function(_credit, method=True, string='Credit'), 'debit': fields.function(_debit, method=True, string='Debit'), 'protected': fields.boolean('Protected'), 'reconcile': fields.boolean('Reconcile'), 'shortcut': fields.char('Shortcut', size=12), 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True), # this prevents the server to initialize on clean db (because account_tax table is not defined yet) # 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id','tax_id', 'Default Taxes'), 'active': fields.boolean('Active'), 'gl_group': fields.boolean('Group in General Ledger'), 'date_maturity': fields.boolean('Maturity Date'), 'note': fields.text('Note') } _defaults = { 'sign': lambda *a: 1, 'type': lambda *a: 'view', 'active': lambda *a: 1, 'reconcile': lambda *a: 0, 'close_method': lambda *a: 'balance', 'protected': lambda *a: False, } def name_search(self, cr, user, name, args=[], operator='ilike', context={}): ids = [] if name: ids = self.search(cr, user, [('code','=',name)]+ args) if not ids: ids = self.search(cr, user, [('name',operator,name)]+ args) return self.name_get(cr, user, ids, context=context) def name_get(self, cr, uid, ids, context={}): if not len(ids): return [] reads = self.read(cr, uid, ids, ['name','code'], context) res = [] for record in reads: name = record['name'] if record['code']: name = record['code']+' - '+name res.append((record['id'],name )) return res account_account() class account_journal_view(osv.osv): _name = "account.journal.view" _description = "Journal View" _columns = { 'name': fields.char('Journal View', size=64, required=True), 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns') } _order = "name" account_journal_view() class account_journal_column(osv.osv): def _col_get(self, cr, user): result = [] cols = self.pool.get('account.move.line')._columns for col in cols: result.append( (col, cols[col].string) ) result.sort() return result _name = "account.journal.column" _description = "Journal Column" _columns = { 'name': fields.char('Column Name', size=64, required=True), 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32), 'view_id': fields.many2one('account.journal.view', 'Journal View'), 'sequence': fields.integer('Sequence'), 'required': fields.boolean('Required'), 'readonly': fields.boolean('Readonly'), } _order = "sequence" account_journal_column() class account_journal(osv.osv): _name = "account.journal" _description = "Journal" _columns = { 'name': fields.char('Journal Name', size=64, required=True), 'code': fields.char('Code', size=3), 'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32), 'active': fields.boolean('Active'), 'view_id': fields.many2one('account.journal.view', 'View', required=True), 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account'), 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account'), 'sequence_id': fields.many2one('ir.sequence', 'Move Sequence'), 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'), } _defaults = { 'active': lambda *a: 1, } def create(self, cr, uid, vals, context={}): journal_id = super(osv.osv, self).create(cr, uid, vals, context) journal_name = self.browse(cr, uid, [journal_id])[0].code periods = self.pool.get('account.period') ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))]) for period in periods.browse(cr, uid, ids): self.pool.get('account.journal.period').create(cr, uid, { 'name': (journal_name or '')+':'+(period.code or ''), 'journal_id': journal_id, 'period_id': period.id }) return journal_id def name_search(self, cr, user, name, args=[], operator='ilike', context={}): ids = [] if name: ids = self.search(cr, user, [('code','ilike',name)]+ args) if not ids: ids = self.search(cr, user, [('name',operator,name)]+ args) return self.name_get(cr, user, ids, context=context) account_journal() class account_organisation(osv.osv): _name = "account.organisation" _description = "Organisation" _columns = { 'name': fields.char('Company Name', size=64, required=True), 'currency_id': fields.many2one('account.currency', 'Default Currency', required=True), 'partner_id': fields.many2one('res.partner', 'Partner', required=True), 'partner_contact_id': fields.many2one('res.partner.address', 'Partner Default Contact', required=True), 'note': fields.text('Note'), 'recall_ids': fields.one2many('account.recall', 'organisation_id', 'Recall'), 'journal_id': fields.many2one('account.journal', 'Journal a nouveau', required=True), 'sale_account_id': fields.many2one('account.account', 'Sale Account', required=True), 'purchase_account_id': fields.many2one('account.account', 'Purchase Account', required=True), 'move_type': fields.selection([('normal','Normal'),('new','New')], 'Move Type', required=True), } account_organisation() class account_recall(osv.osv): _name = "account.recall" _description = "Recall" _columns = { 'name': fields.char('Recall Name', size=64, required=True), 'days_from': fields.integer('Days From', required=True), 'days_to': fields.integer('Days To', required=True), 'organisation_id': fields.many2one('account.organisation', 'Organisation', required=True) } account_recall() class account_bank(osv.osv): _name = "account.bank" _description = "Banks" _columns = { 'name': fields.char('Bank Name', size=64, required=True), 'code': fields.char('Code', size=6), 'partner_id': fields.many2one('res.partner', 'Partner'), 'account_nr': fields.char('Account Nr', size=32), 'journal_id': fields.many2one('account.journal', 'Journal'), 'bank_account_ids': fields.one2many('account.bank.account', 'bank_id', 'Bank Accounts'), 'note': fields.text('Notes'), } _order = "code" account_bank() class account_bank_account(osv.osv): _name = "account.bank.account" _description = "Bank Accounts" _columns = { 'name': fields.char('Bank Name', size=64, required=True), 'code': fields.char('Code', size=6), 'iban': fields.char('Partner', size=24), 'currency_id': fields.many2one('account.currency', 'Currency', required=True), 'account_id': fields.many2one('account.account', 'Account'), 'bank_id': fields.many2one('account.bank', 'Bank'), } _order = "code" account_bank_account() class account_fiscalyear(osv.osv): _name = "account.fiscalyear" _description = "Fiscal Year" _columns = { 'name': fields.char('Fiscal Year', size=64, required=True), 'code': fields.char('Code', size=6, required=True), 'organisation_id': fields.many2one('account.organisation', 'Company'), 'date_start': fields.date('Start date', required=True), 'date_stop': fields.date('End date', required=True), 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'), 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State') } _defaults = { 'state': lambda *a: 'draft', } _order = "code" def create_period3(self,cr, uid, ids, context={}): return self.create_period(cr, uid, ids, context, 3) def create_period(self,cr, uid, ids, context={}, interval=1): for fy in self.browse(cr, uid, ids, context): dt = fy.date_start ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d') while ds.strftime('%Y-%m-%d')=',dt)]) return ids def create(self, cr, uid, vals, context={}): period_id = super(osv.osv, self).create(cr, uid, vals, context) period_name = self.browse(cr, uid, [period_id])[0].code journals = self.pool.get('account.journal') ids = journals.search(cr, uid, []) for journal in journals.browse(cr, uid, ids): self.pool.get('account.journal.period').create(cr, uid, { 'name': (journal.code or journal.name)+':'+(period_name or ''), 'journal_id': journal.id, 'period_id': period_id }) return period_id account_period() class account_journal_period(osv.osv): _name = "account.journal.period" _description = "Journal - Period" def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}): result = {}.fromkeys(ids, 'STOCK_NEW') for r in self.read(cr, uid, ids, ['state']): result[r['id']] = { 'draft': 'STOCK_NEW', 'printed': 'STOCK_PRINT_PREVIEW', 'done': 'STOCK_DIALOG_AUTHENTICATION', }.get(r['state'], 'STOCK_NEW') return result _columns = { 'name': fields.char('Journal-Period Name', size=64, required=True), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"), 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"), 'icon': fields.function(_icon_get, method=True, string='Icon'), 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True) } _defaults = { 'state': lambda *a: 'draft', } _order = "period_id" account_journal_period() #---------------------------------------------------------- # Movements #---------------------------------------------------------- class account_move(osv.osv): _name = "account.move" _description = "Account Move" def _get_period(self, cr, uid, context): periods = self.pool.get('account.period').find(cr, uid) if periods: return periods[0] else: return False _columns = { 'name': fields.char('Move Name', size=64, required=True), 'ref': fields.char('Ref ', size=64), 'period_id': fields.many2one('account.period', 'Period', required=True), 'journal_id': fields.many2one('account.journal', 'Journal', required=True), 'state': fields.selection((('draft','Draft'),('posted','Posted')),'State', required=True), 'line_id': fields.one2many('account.move.line', 'move_id', 'Transactions'), } _defaults = { 'state': lambda *a: 'draft', 'period_id' : _get_period, } def button_validate(self, cr, uid, ids, context={}): self.validate(cr, uid, ids, context) return True def button_cancel(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state':'draft'}, context) return True def write(self, cr, uid, ids, vals, context={}): result = super(osv.osv, self).write(cr, uid, ids, vals, context) for move in self.browse(cr, uid, ids): cr.execute('update account_move_line set journal_id=%d, period_id=%d where move_id=%d', (move.journal_id.id, move.period_id.id, move.id)) return result def create(self, cr, uid, vals, context={}): print vals if 'line_id' in vals: if 'journal_id' in vals: for l in vals['line_id']: if not l[0]: l[2]['journal_id'] = vals['journal_id'] if 'period_id' in vals: for l in vals['line_id']: if not l[0]: l[2]['period_id'] = vals['period_id'] else: default_period = self._get_period(cr, uid, context) for l in vals['line_id']: if not l[0]: l[2]['period_id'] = default_period return super(osv.osv, self).create(cr, uid, vals, context) def _compute_balance(self, cr, uid, id, context={}): move = self.browse(cr, uid, [id])[0] amount = 0 for line in move.line_id: amount+= (line.debit - line.credit) return amount def validate(self,cr, uid, ids, context={}): result = True for move in self.browse(cr, uid, ids): amount = 0 lines = [] for line in move.line_id: amount+= (line.debit - line.credit) lines.append(line.id) if abs(amount)<0.001: self.pool.get('account.move.line').write(cr, uid, lines, {'state':'posted', 'journal_id': move.journal_id.id, 'period_id':move.period_id.id}, context, check=False) self.write(cr, uid, [move.id], {'state':'posted'}, context) else: self.pool.get('account.move.line').write(cr, uid, lines, {'state':'draft'}, context, check=False) self.write(cr, uid, [move.id], {'state':'draft'}, context) result = False return result account_move() class account_move_reconcile(osv.osv): _name = "account.move.reconcile" _description = "Account Reconciliation" _columns = { 'name': fields.char('Name', size=64, required=True), 'type': fields.char('Type', size=16, required=True), 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Move lines'), } _defaults = { 'name': lambda *a: 'reconcile '+time.strftime('%Y-%m-%d') } account_move_reconcile() # # balance_end: should be a field.function # use a sequence for names ? # class account_bank_statement(osv.osv): def _default_account_id(self, cr, uid, context={}): if context.get('account_id',False): return context['account_id'] if context.get('journal_id',False): # TODO: write this return False return False def _default_balance_start(self, cr, uid, context={}): cr.execute('select id from account_bank_statement where account_id=%d order by date desc limit 1', (1,)) res = cr.fetchone() if res: return self.browse(cr, uid, [res[0]], context)[0].balance_end return 0.0 def _end_balance(self, cr, uid, ids, prop, unknow_none, unknow_dict): res = {} statements = self.browse(cr, uid, ids) for statement in statements: res[statement.id] = statement.balance_start for line in statement.line_ids: res[statement.id] += line.debit - line.credit return res _order = "date desc" _name = "account.bank.statement" _description = "Account Reconciliation" _columns = { 'name': fields.char('Name', size=64, required=True), 'date': fields.date('Date'), 'account_id': fields.many2one('account.bank.account', 'Bank Account', required=True), 'balance_start': fields.float('Starting Balance', digits=(16,2)), 'balance_end_real': fields.float('Ending Balance (real)', digits=(16,2)), 'balance_end': fields.function(_end_balance, method=True, string='Ending Balance (computed)'), 'mode': fields.selection([('manual','Manual'),('automatic','Automatic')], 'Creation Mode', required=True), 'line_ids': fields.one2many('account.move.line', 'statement_id', 'Move lines'), 'state': fields.selection([('draft','Draft'),('done','Done')], 'State', required=True), } _defaults = { 'name': lambda *a: 'Statement '+time.strftime('%Y-%m-%d'), 'date': lambda *a: time.strftime('%Y-%m-%d'), 'state': lambda *a: 'draft', 'mode': lambda *a: 'manual', 'balance_start': _default_balance_start, 'account_id': _default_account_id, } account_bank_statement() class account_move_line(osv.osv): _name = "account.move.line" _description = "Account Move Entries" def _default_date_get(self, cr, uid, context): if context.get('journal_id',False) and context.get('period_id',False): cr.execute('select date from account_move_line where journal_id=%d and period_id=%d and create_uid=%d order by id desc limit 1', (context['journal_id'], context['period_id'], uid)) result = cr.fetchone() if result: return result[0] return time.strftime('%Y-%m-%d') def _default_draft_get(field): def _d(self, cr, uid, context): if context.get('journal_id',False) and context.get('period_id',False): cr.execute('select state,'+field+' from account_move_line where journal_id=%d and period_id=%d and create_uid=%d order by id desc limit 1', (context['journal_id'], context['period_id'], uid)) result = cr.fetchone() if result: if result[0]=='draft': return result[1] or False return False return _d def _default_account_get(self, cr, uid, context): if context.get('journal_id',False) and context.get('period_id',False): cr.execute('select state,credit,debit from account_move_line where journal_id=%d and period_id=%d and create_uid=%d order by id desc limit 1', (context['journal_id'], context['period_id'], uid)) result = cr.fetchone() if result: if result[0]=='draft': journal = self.pool.get('account.journal').browse(cr, uid, [context['journal_id']])[0] if result[1]: return journal.default_credit_account_id.id if result[2]: return journal.default_debit_account_id.id return False def _default_debit_get(self, cr, uid, context={}): if not 'journal_id' in context or not 'period_id' in context: return 0.0 cr.execute('select state,move_id from account_move_line where journal_id=%d and period_id=%d and create_uid=%d order by id desc limit 1', (context['journal_id'], context['period_id'], uid)) result = cr.fetchone() if result: if result[0]=='draft': balance = self.pool.get('account.move')._compute_balance(cr, uid, result[1], context) if balance<0: return -balance return 0.0 def _default_credit_get(self, cr, uid, context={}): if not 'journal_id' in context or not 'period_id' in context: return 0.0 cr.execute('select state,move_id from account_move_line where journal_id=%d and period_id=%d and create_uid=%d order by id desc limit 1', (context['journal_id'], context['period_id'], uid)) result = cr.fetchone() if result: if result[0]=='draft': balance = self.pool.get('account.move')._compute_balance(cr, uid, result[1], context) if balance>0: return balance return 0.0 def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict): res={} # TODO group the foreach in sql for id in ids: cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%d', (id,)) dt,acc = cr.fetchone() cr.execute('SELECT SUM(debit-credit) FROM account_move_line WHERE account_id=%d AND (date<%s OR (date=%s AND id<=%d))', (acc,dt,dt,id)) res[id] = cr.fetchone()[0] return res _columns = { 'name': fields.char('Name', size=64, required=True), 'quantity': fields.float('Quantity', digits=(16,2)), 'debit': fields.float('Debit', digits=(16,2), states={'reconciled':[('readonly',True)]}), 'credit': fields.float('Credit', digits=(16,2), states={'reconciled':[('readonly',True)]}), 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}), 'move_id': fields.many2one('account.move', 'Move', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}), 'ref': fields.char('Ref.', size=16), 'statement_id': fields.many2one('account.bank.statement', 'Statement'), 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', states={'reconciled':[('readonly',True)]}), 'state': fields.selection((('draft','Draft'),('posted','Posted'),('reconciled','Reconciled')), 'State', required=True, readonly=True), 'amount_currency': fields.float('Amount Currency'), 'currency_id': fields.many2one('account.currency', 'Currency'), 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True), 'blocked': fields.boolean('Litigation'), 'partner_id': fields.many2one('res.partner', 'Partner Ref.', states={'reconciled':[('readonly',True)]}), 'date_maturity': fields.date('Maturity date', states={'reconciled':[('readonly',True)]}), 'date': fields.date('Effective date', required=True), 'date_created': fields.date('Creation date'), 'balance': fields.function(_balance, method=True, string='Balance') } _defaults = { 'state': lambda *a: 'draft', 'blocked': lambda *a: False, 'move_id': _default_draft_get('move_id'), 'date': _default_date_get, 'name': _default_draft_get('name'), 'credit': _default_credit_get, 'debit': _default_debit_get, 'ref': _default_draft_get('ref'), 'account_id': _default_account_get, 'date_created': lambda *a: time.strftime('%Y-%m-%d'), } _order = "date desc,id desc" # cascade delete account_moves and account_move_reconciles when account_move_lines are deleted _sql = """ create or replace rule "_account_move_reconcile" as on delete to account_move_line do delete from account_move_reconcile where id=OLD.reconcile_id; create or replace rule "_account_move_line" as on delete to account_move_line do delete from account_move where id=OLD.move_id; """ #alter table account_move_line add constraint account_move_line_credit_check check (credit>=0); #alter table account_move_line add constraint account_move_line_debit_check check (debit>=0); # # TODO: Should compute amount if account is in a tax definition # def onchange_account_id(self, cr, uid, ids, move_id, account_id, context={}): print 'account_id', account_id, move_id if not account_id or not move_id: return {} print 'account_id', account_id, move_id balance = self.pool.get('account.move')._compute_balance(cr, uid, move_id, context) field = 'account_collected_id' if balance<0: field = 'account_paid_id' cr.execute('select id from account_tax where '+field+'=%d', (account_id,)) ids = cr.fetchall() if ids: r = self.pool.get('account.tax').compute(cr, uid, map(lambda x:x[0],ids), abs(balance), 1) if r: debit=0 credit=0 if r[0]['amount']<0: credit = abs( r[0]['amount']) else: debit = r[0]['amount'] return {'value':{'debit':debit, 'credit':credit}} return {} def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, context={}): if not partner_id or not move_id: return {} balance = self.pool.get('account.move')._compute_balance(cr, uid, move_id, context) val = {} if balance>0: if balance: val['credit'] = balance val['debit'] = 0 val['account_id'] = ir.ir_get(cr,uid,'meta','account.payable', [('res.partner',partner_id)])[0][2] else: if balance: val['debit'] = -balance val['credit'] = 0 val['account_id'] = ir.ir_get(cr,uid,'meta','account.receivable', [('res.partner',partner_id)])[0][2] return {'value':val} def reconcile(self, cr, uid, ids, type='auto'): id_set=','.join(map(str,ids)) date = time.strftime('%Y-%m-%d') cr.execute('SELECT account_id,reconcile_id FROM account_move_line WHERE id IN ('+id_set+') GROUP BY account_id,reconcile_id') r=cr.fetchall() #TODO: move this check to a constraint in the account_move_reconcile object if len(r)!=1: raise 'Transactions are not of the same account !' if r[0][1]!=None: raise 'Some transactions are already reconciled !' account_id=r[0][0] id = self.pool.get('account.move.reconcile').create(cr, uid, {'name':date, 'type':type, 'line_id':map(lambda x: (4,x,False), ids)}) # the id of the move.reconcile is written in the move.line (self) by the create method above # because of the way the line_id are defined: (4, x, False) self.write(cr,uid,ids,{'state':'c'}) return id def view_header_get(self, cr, user, view_id, view_type, context): if (not context.get('journal_id', False)) or (not context.get('period_id', False)): return False cr.execute('select code from account_journal where id=%d', (context['journal_id'],)) j = cr.fetchone()[0] or '' cr.execute('select code from account_period where id=%d', (context['period_id'],)) p = cr.fetchone()[0] or '' return j+':'+p def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}): result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context) if view_type=='tree' and 'journal_id' in context: state = '' title = self.view_header_get(cr, uid, view_id, view_type, context) journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id']) for field in journal.view_id.columns_id: if field.field=='state': state=' colors="red:state==\'draft\'"' xml = '''\n\n\t''' % (title,state) fields = [] for field in journal.view_id.columns_id: fields.append(field.field) attrs = [] if field.readonly: attrs.append('readonly="1"') if field.required: attrs.append('required="1"') else: attrs.append('required="0"') if field.field == 'partner_id': attrs.append('on_change="onchange_partner_id(move_id,partner_id)"') if field.field == 'account_id': attrs.append('on_change="onchange_account_id(move_id,account_id)"') xml += '''\n''' % (field.field,' '.join(attrs)) xml += '''''' result['arch'] = xml result['fields'] = self.fields_get(cr, uid, fields, context) return result def write(self, cr, uid, ids, vals, context={}, check=True): result = super(osv.osv, self).write(cr, uid, ids, vals, context) if check: done = [] for move in self.browse(cr, uid, ids): if move.move_id.id not in done: done.append( move.move_id.id) self.pool.get('account.move').validate(cr, uid, [move.move_id.id], context) return result def create(self, cr, uid, vals, context={}): if not vals.get('move_id', False): journal = self.pool.get('account.journal').browse(cr, uid, [context['journal_id']])[0] if journal.sequence_id: # name = journal.sequence_id.get_id() name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id) v = { 'name': name, 'period_id': context['period_id'], 'journal_id': context['journal_id'] } move_id = self.pool.get('account.move').create(cr, uid, v, context) vals['move_id'] = move_id else: raise osv.except_osv('No piece number !', 'Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.') result = super(osv.osv, self).create(cr, uid, vals, context) self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context) return result account_move_line() #---------------------------------------------------------- # Tax #---------------------------------------------------------- """ a documenter child_depend: la taxe depend des taxes filles """ class account_tax(osv.osv): """ A tax object. Type: percent, fixed, none, code PERCENT: tax = price * amount FIXED: tax = price + amount NONE: no tax line CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object} return result in the context Ex: result=round(price_unit*0.21,4) """ _name = 'account.tax' _description = 'Tax' _columns = { 'name': fields.char('Tax Name', size=64, required=True), 'amount': fields.float('Amount', required=True, digits=(14,4)), 'active': fields.boolean('Active'), 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True), 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True), 'domain':fields.char('Domain', size=32), 'account_collected_id':fields.many2one('account.account', 'Collected Tax Account', required=True), 'account_paid_id':fields.many2one('account.account', 'Paid Tax Account', required=True), 'parent_id':fields.many2one('account.tax', 'Parent Tax Account'), 'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'), 'child_depend':fields.boolean('Tax depends on its children'), 'python_compute':fields.text('Python Code'), 'python_applicable':fields.text('Python Code'), } _defaults = { 'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n\nresult = price_unit * 0.10''', 'applicable_type': lambda *a: 'true', 'type': lambda *a: 'percent', 'amount': lambda *a: 19.6, 'active': lambda *a: 1, } def applicable(self, cr, uid, ids, price_unit, address_id=None): if not ids: return [] res = [] for tax in self.read(cr, uid, ids): if tax['applicable_type']=='code': localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id)[0]} exec tax['python_applicable'] in localdict if localdict.get('result', False): res.append(tax['id']) else: res.append(tax['id']) return res def _unit_compute(self, cr, uid, ids, price_unit, address_id=None): ids = self.applicable(cr, uid, ids, price_unit, address_id) if not len(ids): return [] # fetches the objects with the given ids taxes = self.read(cr, uid, ids) res = [] for tax in taxes: childs = self._unit_compute(cr, uid, tax['child_ids'], price_unit, address_id) amount_child_taxes = reduce(lambda x,y: x + y['amount'], childs, 0.0) # then we calculate the amount for the current tax object and append it to the result if tax['type']=='percent': amount = round((price_unit+amount_child_taxes)*tax['amount'],4) res.append({'id':tax['id'],'name':tax['name'], 'amount':amount, 'account_collected_id':tax['account_collected_id'], 'account_paid_id':tax['account_paid_id']}) elif tax['type']=='fixed': res.append({'id':tax['id'],'name':tax['name'], 'amount':tax['amount'], 'account_collected_id':tax['account_collected_id'], 'account_paid_id':tax['account_paid_id']}) elif tax['type']=='code': if address_id: localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id)[0]} else: localdict = {'price_unit':price_unit, 'address':None} exec tax['python_compute'] in localdict amount = localdict['result'] res.append({'id':tax['id'],'name':tax['name'], 'amount':amount, 'account_collected_id':tax['account_collected_id'], 'account_paid_id':tax['account_paid_id']}) res.extend(childs) return res def compute(self, cr, uid, ids, price_unit, quantity, address_id=None): """ Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID. RETURN: [ tax ] tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2} one tax for each IDS tax and their childs """ res = self._unit_compute(cr, uid, ids, price_unit, address_id) for r in res: r['amount'] *= quantity return res account_tax() # --------------------------------------------------------- # Budgets # --------------------------------------------------------- class account_budget_post(osv.osv): _name = 'account.budget.post' _description = 'Budget Post' _columns = { 'code': fields.char('Number', size=64, required=True), 'name': fields.char('Label', size=256, required=True), 'parent_id': fields.many2one('account.budget.post', 'Parent item'), 'sens': fields.selection( [('charge','Charge'), ('produit','Product')], 'Direction', required=True), 'dotation_ids': fields.one2many('account.budget.post.dotation', 'post_id', 'Expenses'), 'account_ids': fields.many2many('account.account', 'account_budget_rel', 'budget_id', 'account_id', 'Accounts'), } def spread(self, cr, uid, ids, fiscalyear_id=False, quantity=0.0, amount=0.0): dobj = self.pool.get('account.budget.post.dotation') for o in self.browse(cr, uid, ids): # delete dotations for this post dobj.unlink(cr, uid, dobj.search(cr, uid, [('post_id','=',o.id)])) # create one dotation per period in the fiscal year, and spread the total amount/quantity over those dotations fy = self.pool.get('account.fiscalyear').browse(cr, uid, [fiscalyear_id])[0] num = len(fy.period_ids) for p in fy.period_ids: dobj.create(cr, uid, {'post_id': o.id, 'period_id': p.id, 'quantity': quantity/num, 'amount': amount/num}) return True account_budget_post() class account_budget_post_dotation(osv.osv): _name = 'account.budget.post.dotation' _description = "Dotation pour un poste budgétaire" _columns = { 'name': fields.char('Name', size=64), 'post_id': fields.many2one('account.budget.post', 'Item'), 'period_id': fields.many2one('account.period', 'Period'), 'quantity': fields.float('Quantite', digits=(16,2)), 'amount': fields.float('Montant', digits=(16,2)), } account_budget_post_dotation() # --------------------------------------------------------- # Account Move Models # --------------------------------------------------------- class account_model(osv.osv): _name = "account.model" _description = "Account Model" _columns = { 'name': fields.char('Move Name', size=64, required=True), 'ref': fields.char('Ref', size=64), 'journal_id': fields.many2one('account.journal', 'Journal', required=True), 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Transactions'), } def generate(self, cr, uid, ids, datas={}, context={}): move_ids = [] for model in self.browse(cr, uid, ids, context): period_id = self.pool.get('account.period').find(cr,uid, context=context) if not period_id: raise osv.except_osv('No period found !', 'Unable to find a valid period !') period_id = period_id[0] move_id = self.pool.get('account.move').create(cr, uid, { 'name':model.name, 'ref':model.ref, 'period_id': period_id, 'journal_id': model.journal_id.id, }) move_ids.append(move_id) for line in model.lines_id: val = { 'move_id': move_id, 'journal_id': model.journal_id.id, 'period_id': period_id } val.update({ 'name': line.name, 'quantity': line.quantity, 'debit': line.debit, 'credit': line.credit, 'account_id': line.account_id.id, 'move_id': move_id, 'ref': line.ref, 'partner_id': line.partner_id.id, 'date': time.strftime('%Y-%m-%d'), 'date_maturity': time.strftime('%Y-%m-%d') }) self.pool.get('account.move.line').create(cr, uid, val, context=context) return move_ids account_model() class account_model_line(osv.osv): _name = "account.model.line" _description = "Account Model Entries" _columns = { 'name': fields.char('Name', size=64, required=True), 'sequence': fields.integer('Sequence', required=True), 'quantity': fields.float('Quantity', digits=(16,2)), 'debit': fields.float('Debit', digits=(16,2)), 'credit': fields.float('Credit', digits=(16,2)), 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"), 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade"), 'ref': fields.char('Ref.', size=16), 'amount_currency': fields.float('Amount Currency'), 'currency_id': fields.many2one('account.currency', 'Currency'), 'partner_id': fields.many2one('res.partner', 'Partner Ref.'), 'date_maturity': fields.selection([('today','Today'), ('partner','Partner Payment Term')], 'Maturity date'), 'date': fields.selection([('today','Today'), ('partner','Partner Payment Term')], 'Current Date', required=True), } _defaults = { } _order = 'sequence' account_model_line() # --------------------------------------------------------- # Account Subscription # --------------------------------------------------------- class account_subscription(osv.osv): _name = "account.subscription" _description = "Account Subscription" _columns = { 'name': fields.char('Name', size=64, required=True), 'ref': fields.char('Ref.', size=16), 'model_id': fields.many2one('account.model', 'Model', required=True), 'date_start': fields.date('Starting date', required=True), 'period_total': fields.integer('Number of period', required=True), 'period_nbr': fields.integer('Period', required=True), 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True), 'amount': fields.float('Amount', size=(16,2), required=True), 'amount_type': fields.selection([('global','Global Amount'),('period','Per Period Amount')], 'Amount Type', required=True), 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True), 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines') } _defaults = { 'date_start': lambda *a: time.strftime('%Y-%m-%d'), 'amount_type': lambda *a: 'period', 'period_type': lambda *a: 'month', 'period_total': lambda *a: 12, 'period_nbr': lambda *a: 1, 'state': lambda *a: 'draft', } def state_draft(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state':'draft'}) return False def check(self, cr, uid, ids, context={}): todone = [] for sub in self.browse(cr, uid, ids, context): ok = True for line in sub.lines_id: if not line.move_id.id: ok = False break if ok: todone.append(sub.id) if len(todone): self.write(cr, uid, todone, {'state':'done'}) return False def remove_line(self, cr, uid, ids, context={}): toremove = [] for sub in self.browse(cr, uid, ids, context): for line in sub.lines_id: if not line.move_id.id: toremove.append(line.id) if len(toremove): self.pool.get('account.subscription.line').unlink(cr, uid, toremove) return True self.write(cr, uid, ids, {'state':'draft'}) return False def compute(self, cr, uid, ids, context={}): for sub in self.browse(cr, uid, ids, context): ds = sub.date_start amount = sub.amount if sub.amount_type=='global': amount = amount/ sub.period_total for i in range(sub.period_total): self.pool.get('account.subscription.line').create(cr, uid, { 'date': ds, 'subscription_id': sub.id, 'amount': amount }) if sub.period_type=='day': ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d') if sub.period_type=='month': ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d') if sub.period_type=='year': ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d') self.write(cr, uid, ids, {'state':'running'}) return True account_subscription() class account_subscription_line(osv.osv): _name = "account.subscription.line" _description = "Account Subscription Line" _columns = { 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True), 'date': fields.date('Date', required=True), 'amount': fields.float('Amount', required=True), 'move_id': fields.many2one('account.move', 'Move'), } _defaults = { } def move_create(self, cr, uid, ids, context={}): tocheck = {} for line in self.browse(cr, uid, ids, context): datas = { 'date': line.date, 'amount': line.amount } ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context) tocheck[line.subscription_id.id] = True self.write(cr, uid, [line.id], {'move_id':ids[0]}) if tocheck: self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context) return True _rec_name = 'date' account_subscription_line()