trytond-account_budget-zz/account_budget.py

340 lines
12 KiB
Python

# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
import datetime
from decimal import Decimal
from sql.aggregate import Sum
from sql.operators import Abs, In
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.modules.jasper_reports.jasper import JasperReport
__all__ = ['BudgetAccounts', 'BudgetPosition', 'Budget', 'BudgetLine',
'BudgetReport', 'PrintBudget', 'PrintBudgetStart']
_STATES = {
'readonly': Eval('state') != 'draft',
}
_DEPENDS = ['state']
class BudgetAccounts(ModelSQL):
'AccountBudget - Accounts'
__name__ = 'account_budget_post-account_account'
_table = 'account_budget_rel'
budget = fields.Many2One('account.budget.position',
'Budgetary Postition', ondelete='CASCADE', select=True, required=True)
account = fields.Many2One('account.account', 'Account',
ondelete='CASCADE', select=True, required=True)
class BudgetPosition(ModelSQL, ModelView):
'Budgetary Position'
__name__ = 'account.budget.position'
company = fields.Many2One('company.company', 'Company', required=True,
ondelete='RESTRICT')
code = fields.Char('Code', required=True)
name = fields.Char('Name', required=True)
accounts = fields.Many2Many('account_budget_post-account_account',
'budget', 'account', 'Accounts', domain=[('type', '!=', 'view')])
lines = fields.One2Many('account.budget.line', 'general_budget',
'Budget Lines', readonly=True)
@classmethod
def __setup__(cls):
super(BudgetPosition, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
@staticmethod
def default_company():
return Transaction().context.get('company') or None
class Budget(Workflow, ModelSQL, ModelView):
'Budget'
__name__ = 'account.budget'
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('validated', 'Validated'),
('done', 'Done'),
('cancel', 'Cancelled')],
'Status', select=True, required=True, readonly=True)
company = fields.Many2One('company.company', 'Company', required=True,
ondelete='RESTRICT', states=_STATES, depends=_DEPENDS)
name = fields.Char('Name', required=True, states=_STATES, depends=_DEPENDS)
code = fields.Char('Code', required=True, states=_STATES, depends=_DEPENDS)
validating_user = fields.Many2One('res.user', 'Validate User',
readonly=True)
start_date = fields.Date('Start Date', required=True, states=_STATES,
depends=_DEPENDS)
end_date = fields.Date('End Date', required=True, states=_STATES,
depends=_DEPENDS)
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
'readonly': (Eval('state') != 'draft'),
}, depends=['state'])
lines = fields.One2Many('account.budget.line', 'budget',
'Budget Lines', states=_STATES, depends=_DEPENDS)
@classmethod
def __setup__(cls):
super(Budget, cls).__setup__()
cls._transition_state = 'state'
cls._transitions |= set((
('draft', 'confirmed'),
('confirmed', 'validated'),
('confirmed', 'cancel'),
('validated', 'done'),
('validated', 'cancel'),
('cancel', 'draft'),
))
cls._buttons.update({
'confirm': {
'invisible': Eval('state') != 'draft',
},
'valid': {
'invisible': Eval('state') != 'confirmed',
},
'done': {
'invisible': Eval('state') != 'validated',
},
'draft': {
'invisible': Eval('state') != 'cancel',
},
'cancel': {
'invisible': ~Eval('state').in_(['validated',
'confirmed']),
},
})
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_company():
return Transaction().context.get('company') or None
@staticmethod
def default_currency():
Company = Pool().get('company.company')
if Transaction().context.get('company'):
company = Company(Transaction().context['company'])
return company.currency.id
def get_rec_name(self, name):
return "%s - %s" % (self.code, self.name)
@classmethod
def search_rec_name(cls, name, clause):
budgets = cls.search([('code',) + tuple(clause[1:])], order=[])
if budgets:
budgets += cls.search([('name',) + tuple(clause[1:])], order=[])
return [('id', 'in', [budget.id for budget in budgets])]
return [('name',) + tuple(clause[1:])]
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, budgets):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, budgets):
pass
@classmethod
@ModelView.button
@Workflow.transition('validated')
def valid(cls, budgets):
cls.write(budgets, {
'validating_user': Transaction().user,
})
@classmethod
@ModelView.button
@Workflow.transition('cancel')
def cancel(cls, budgets):
pass
@classmethod
@ModelView.button
@Workflow.transition('done')
def done(cls, budgets):
pass
class BudgetLine(ModelSQL, ModelView):
'Budget Line'
__name__ = 'account.budget.line'
budget = fields.Many2One('account.budget', 'Budget',
ondelete='CASCADE', select=True, required=True)
general_budget = fields.Many2One('account.budget.position',
'Budgetary Position', required=True)
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
paid_date = fields.Date('Paid Date')
planned_amount = fields.Numeric('Planned Amount', required=True,
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'])
practical_amount = fields.Function(fields.Numeric('Practical Amount',
depends=['currency_digits'], digits=(16, Eval('currency_digits', 2))),
'get_practical_amount')
theoritical_amount = fields.Function(fields.Numeric('Theoretical Amount',
depends=['currency_digits'], digits=(16, Eval('currency_digits', 2))),
'get_theoritical_amount')
percentage = fields.Function(fields.Numeric('Percentage', digits=(16, 2)),
'get_percentage')
@classmethod
def __setup__(cls):
super(BudgetLine, cls).__setup__()
cls._error_messages.update({
'no_accounts': ('The General Budget "%s" has no accounts.'),
})
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
if self.budget and self.budget.currency:
return self.budget.currency.digits
return 2
def get_practical_amount(self, name):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
transaction = Transaction()
cursor = transaction.cursor
context = transaction.context
line = Line.__table__()
move = Move.__table__()
accounts = [x.id for x in self.general_budget.accounts]
if not accounts:
self.raise_user_error('no_accounts', self.general_budget.rec_name)
end_date = context.get('end_date', self.end_date)
start_date = context.get('start_date', self.start_date)
cursor.execute(*line.join(move, condition=line.move == move.id).select(
Sum(Abs(line.credit - line.debit)),
where=((move.date >= start_date) & (move.date <= end_date) &
In(line.account, accounts))))
result = cursor.fetchone()
if result and result[0]:
return result[0]
return Decimal('0.0')
def get_theoritical_amount(self, name):
transaction = Transaction()
context = transaction.context
end_date = datetime.date.today()
start_date = self.start_date
end_date = context.get('end_date', self.end_date)
start_date = context.get('start_date', datetime.date.today())
if self.paid_date:
if self.end_date <= self.paid_date:
return Decimal('0.0')
else:
return self.planned_amount
else:
total = self.end_date - self.start_date
elapsed = (min(self.end_date, end_date) -
max(self.start_date, start_date))
if end_date < self.start_date:
elapsed = self.start_date - end_date
if total.days:
return ((Decimal(str(elapsed.days)) / Decimal(str(total.days)))
* self.planned_amount)
else:
return self.planned_amount
return Decimal('0.0')
def get_percentage(self, name):
if self.theoritical_amount != Decimal('0.0'):
return ((self.practical_amount or Decimal('0.0')) /
self.theoritical_amount) * Decimal('100.0')
return Decimal('0.0')
class BudgetReport(JasperReport):
'Budget Report'
__name__ = 'account.budget.report'
@classmethod
def execute(cls, ids, data):
pool = Pool()
transaction = Transaction()
Budget = pool.get('account.budget')
BudgetLine = pool.get('account.budget.line')
budget = Budget(transaction.context['active_id'])
parameters = {}
parameters['start_date'] = str(data['start_date'])
parameters['end_date'] = str(data['end_date'])
parameters['budget'] = budget.rec_name
parameters['currency'] = budget.currency.rec_name
with transaction.set_context(start_date=data['start_date'],
end_date=data['end_date']):
ids = [x.id for x in BudgetLine.search(
[('budget', '=', budget.id)])]
return super(BudgetReport, cls).execute(ids, {
'name': 'account_budget.budget',
'parameters': parameters,
'output_format': 'PDF',
})
class PrintBudgetStart(ModelView):
'Print Budget Start'
__name__ = 'account_budget.print_budget.start'
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
@staticmethod
def default_start_date():
today = datetime.datetime.today()
return datetime.datetime(today.year, 1, 1)
@staticmethod
def default_end_date():
return datetime.datetime.today()
class PrintBudget(Wizard):
'Print Budget'
__name__ = 'account_budget.print_budget'
start = StateView('account_budget.print_budget.start',
'account_budget.print_budget_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateAction('account_budget.act_report_budget')
def do_print_(self, action):
data = {
'start_date': self.start.start_date,
'end_date': self.start.end_date,
}
return action, data
def transition_print_(self):
return 'end'