Active Record

This commit is contained in:
Cédric Krier committed 2012-09-24 22:17:32 +02:00
1 parent e7a05236a7
commit 83d9c368f7
6 files changed
+1237 -1435

No files matched your search

+30
View File
@@ -1,7 +1,37 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from .payment_term import *
from .invoice import *
from .party import *
from .account import *
def register():
Pool.register(
PaymentTerm,
PaymentTermLine,
Invoice,
InvoicePaymentLine,
InvoiceLine,
InvoiceLineTax,
InvoiceTax,
PrintInvoiceWarning,
PayInvoiceStart,
PayInvoiceAsk,
CreditInvoiceStart,
Address,
Party,
FiscalYear,
Period,
Reconciliation,
module='account_invoice', type_='model')
Pool.register(
PrintInvoice,
PayInvoice,
CreditInvoice,
module='account_invoice', type_='wizard')
Pool.register(
InvoiceReport,
module='account_invoice', type_='report')
+96 -105
View File
@@ -1,12 +1,15 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.model import Model, ModelView, ModelSQL, fields
from trytond.model import fields
from trytond.pyson import Eval
from trytond.pool import Pool
from trytond.pool import Pool, PoolMeta
__all__ = ['FiscalYear', 'Period', 'Reconciliation']
__metaclass__ = PoolMeta
class FiscalYear(ModelSQL, ModelView):
_name = 'account.fiscalyear'
class FiscalYear:
__name__ = 'account.fiscalyear'
out_invoice_sequence = fields.Many2One('ir.sequence.strict',
'Customer Invoice Sequence', required=True,
domain=[
@@ -62,12 +65,13 @@ class FiscalYear(ModelSQL, ModelView):
'company': Eval('company'),
}, depends=['company'])
def __init__(self):
super(FiscalYear, self).__init__()
self._constraints += [
@classmethod
def __setup__(cls):
super(FiscalYear, cls).__setup__()
cls._constraints += [
('check_invoice_sequences', 'different_invoice_sequence'),
]
self._error_messages.update({
]
cls._error_messages.update({
'change_invoice_sequence': 'You can not change ' \
'the invoice sequence if there is already ' \
'an invoice opened in the fiscalyear',
@@ -75,44 +79,41 @@ class FiscalYear(ModelSQL, ModelView):
'invoice sequences per fiscal year!',
})
def check_invoice_sequences(self, ids):
for fiscalyear in self.browse(ids):
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if self.search([
(sequence, '=', fiscalyear[sequence].id),
('id', '!=', fiscalyear.id),
]):
return False
def check_invoice_sequences(self):
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if self.search([
(sequence, '=', getattr(self, sequence).id),
('id', '!=', self.id),
]):
return False
return True
def write(self, ids, vals):
invoice_obj = Pool().get('account.invoice')
if isinstance(ids, (int, long)):
ids = [ids]
@classmethod
def write(cls, fiscalyears, vals):
Invoice = Pool().get('account.invoice')
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if vals.get(sequence):
for fiscalyear in self.browse(ids):
if fiscalyear[sequence] and \
fiscalyear[sequence].id != \
vals[sequence]:
if invoice_obj.search([
('invoice_date', '>=', fiscalyear.start_date),
('invoice_date', '<=', fiscalyear.end_date),
('number', '!=', None),
('type', '=', sequence[:-9]),
]):
self.raise_user_error('change_invoice_sequence')
return super(FiscalYear, self).write(ids, vals)
FiscalYear()
for fiscalyear in fiscalyears:
if (getattr(fiscalyear, sequence)
and (getattr(fiscalyear, sequence).id !=
vals[sequence])):
if Invoice.search([
('invoice_date', '>=',
fiscalyear.start_date),
('invoice_date', '<=',
fiscalyear.end_date),
('number', '!=', None),
('type', '=', sequence[:-9]),
]):
cls.raise_user_error('change_invoice_sequence')
super(FiscalYear, cls).write(fiscalyears, vals)
class Period(ModelSQL, ModelView):
_name = 'account.period'
class Period:
__name__ = 'account.period'
out_invoice_sequence = fields.Many2One('ir.sequence.strict',
'Customer Invoice Sequence',
domain=[('code', '=', 'account.invoice')],
@@ -150,12 +151,13 @@ class Period(ModelSQL, ModelView):
},
depends=['type'])
def __init__(self):
super(Period, self).__init__()
self._constraints += [
@classmethod
def __setup__(cls):
super(Period, cls).__setup__()
cls._constraints += [
('check_invoice_sequences', 'check_invoice_sequences'),
]
self._error_messages.update({
]
cls._error_messages.update({
'change_invoice_sequence': 'You can not change ' \
'the invoice sequence if there is already ' \
'an invoice opened in the period',
@@ -164,86 +166,75 @@ class Period(ModelSQL, ModelView):
'in the same company!',
})
def check_invoice_sequences(self, ids):
for period in self.browse(ids):
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if self.search([
(sequence, '=', period[sequence].id),
('fiscalyear', '!=', period.fiscalyear.id),
]):
return False
if period[sequence].company \
and period[sequence].company.id != \
period.fiscalyear.company.id:
return False
def check_invoice_sequences(self):
for sequence_name in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
sequence = getattr(self, sequence_name)
if self.search([
(sequence_name, '=', sequence.id),
('fiscalyear', '!=', self.fiscalyear.id),
]):
return False
if (sequence.company
and sequence.company != self.fiscalyear.company):
return False
return True
def create(self, vals):
fiscalyear_obj = Pool().get('account.fiscalyear')
@classmethod
def create(cls, vals):
FiscalYear = Pool().get('account.fiscalyear')
vals = vals.copy()
if vals.get('fiscalyear'):
fiscalyear = fiscalyear_obj.browse(vals['fiscalyear'])
fiscalyear = FiscalYear(vals['fiscalyear'])
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if not vals.get(sequence):
vals[sequence] = fiscalyear[sequence].id
return super(Period, self).create(vals)
vals[sequence] = getattr(fiscalyear, sequence).id
return super(Period, cls).create(vals)
def write(self, ids, vals):
invoice_obj = Pool().get('account.invoice')
@classmethod
def write(cls, periods, vals):
Invoice = Pool().get('account.invoice')
if isinstance(ids, (int, long)):
ids = [ids]
for sequence in ('out_invoice_sequence', 'in_invoice_sequence',
for sequence_name in ('out_invoice_sequence', 'in_invoice_sequence',
'out_credit_note_sequence', 'in_credit_note_sequence'):
if vals.get(sequence):
for period in self.browse(ids):
if period[sequence] and \
period[sequence].id != \
vals[sequence]:
if invoice_obj.search([
('invoice_date', '>=', period.start_date),
('invoice_date', '<=', period.end_date),
('number', '!=', None),
('type', '=', sequence[:-9]),
]):
self.raise_user_error('change_invoice_sequence')
return super(Period, self).write(ids, vals)
Period()
if vals.get(sequence_name):
for period in periods:
sequence = getattr(period, sequence_name)
if (sequence and
sequence.id != vals[sequence]):
if Invoice.search([
('invoice_date', '>=', period.start_date),
('invoice_date', '<=', period.end_date),
('number', '!=', None),
('type', '=', sequence_name[:-9]),
]):
cls.raise_user_error('change_invoice_sequence')
super(Period, cls).write(periods, vals)
class Reconciliation(Model):
_name = 'account.move.reconciliation'
class Reconciliation:
__name__ = 'account.move.reconciliation'
def create(self, values):
pool = Pool()
invoice_obj = pool.get('account.invoice')
@classmethod
def create(cls, values):
Invoice = Pool().get('account.invoice')
result = super(Reconciliation, self).create(values)
reconciliation = self.browse(result)
reconciliation = super(Reconciliation, cls).create(values)
move_ids = set(l.move.id for l in reconciliation.lines)
invoice_ids = invoice_obj.search([
invoices = Invoice.search([
('move', 'in', list(move_ids)),
])
invoice_obj.process(invoice_ids)
return result
Invoice.process(invoices)
return reconciliation
def delete(self, ids):
pool = Pool()
invoice_obj = pool.get('account.invoice')
@classmethod
def delete(cls, reconciliations):
Invoice = Pool().get('account.invoice')
if isinstance(ids, (int, long)):
ids = [ids]
reconciliations = self.browse(ids)
move_ids = set(l.move.id for r in reconciliations for l in r.lines)
invoice_ids = invoice_obj.search([
invoices = Invoice.search([
('move', 'in', list(move_ids)),
])
result = super(Reconciliation, self).delete(ids)
invoice_obj.process(invoice_ids)
return result
Reconciliation()
super(Reconciliation, cls).delete(reconciliations)
Invoice.process(invoices)
+1000 -1206
View File
File diff suppressed because it is too large. Load diff
+14 -14
View File
@@ -1,37 +1,37 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.pool import Pool, PoolMeta
__all__ = ['Address', 'Party']
__metaclass__ = PoolMeta
class Address(ModelSQL, ModelView):
_name = 'party.address'
class Address:
__name__ = 'party.address'
invoice = fields.Boolean('Invoice')
Address()
class Party(ModelSQL, ModelView):
_name = 'party.party'
__name__ = 'party.party'
customer_payment_term = fields.Property(fields.Many2One(
'account.invoice.payment_term', string='Customer Payment Term'))
supplier_payment_term = fields.Property(fields.Many2One(
'account.invoice.payment_term', string='Supplier Payment Term'))
def init(self, module_name):
ir_model_field_obj = Pool().get('ir.model.field')
@classmethod
def __register__(cls, module_name):
ModelField = Pool().get('ir.model.field')
# Migration from 2.2: property field payment_term renamed
# to customer_payment_term
field_ids = ir_model_field_obj.search([
fields = ModelField.search([
('name', '=', 'payment_term'),
('model.model', '=', 'party.party')
])
if field_ids:
ir_model_field_obj.write(field_ids, {
if fields:
ModelField.write(fields, {
'name': 'customer_payment_term',
})
super(Party, self).init(module_name)
Party()
super(Party, cls).__register__(module_name)
+92 -103
View File
@@ -8,11 +8,12 @@ from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.pool import Pool
__all__ = ['PaymentTerm', 'PaymentTermLine']
class PaymentTerm(ModelSQL, ModelView):
'Payment Term'
_name = 'account.invoice.payment_term'
_description = __doc__
__name__ = 'account.invoice.payment_term'
name = fields.Char('Payment Term', size=None, required=True,
translate=True)
active = fields.Boolean('Active')
@@ -20,41 +21,39 @@ class PaymentTerm(ModelSQL, ModelView):
lines = fields.One2Many('account.invoice.payment_term.line', 'payment',
'Lines')
def __init__(self):
super(PaymentTerm, self).__init__()
self._order.insert(0, ('name', 'ASC'))
self._error_messages.update({
@classmethod
def __setup__(cls):
super(PaymentTerm, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._error_messages.update({
'invalid_line': 'Invalid payment term line!',
'missing_remainder': 'Payment term missing a remainder line!',
})
def default_active(self):
@staticmethod
def default_active():
return True
def compute(self, amount, currency, payment_term, date=None):
def compute(self, amount, currency, date=None):
"""Calculate payment terms and return a list of tuples
with (date, amount) for each payment term line.
amount must be a Decimal used for the calculation and
both currency and payment_term must be BrowseRecord. If
specified, date will be used as the start date, otherwise
current date will be used.
amount must be a Decimal used for the calculation.
If specified, date will be used as the start date, otherwise current
date will be used.
"""
#TODO implement business_days
# http://pypi.python.org/pypi/BusinessHours/
pool = Pool()
line_obj = pool.get('account.invoice.payment_term.line')
currency_obj = pool.get('currency.currency')
date_obj = pool.get('ir.date')
Date = Pool().get('ir.date')
sign = 1 if amount >= Decimal('0.0') else -1
res = []
if date is None:
date = date_obj.today()
date = Date.today()
remainder = amount
for line in payment_term.lines:
value = line_obj.get_value(line, remainder, amount, currency)
value_date = line_obj.get_date(line, date)
for line in self.lines:
value = line.get_value(remainder, amount, currency)
value_date = line.get_date(date)
if not value or not value_date:
if (not remainder) and line.amount:
self.raise_user_error('invalid_line')
@@ -66,17 +65,14 @@ class PaymentTerm(ModelSQL, ModelView):
res.append((value_date, value))
remainder -= value
if not currency_obj.is_zero(currency, remainder):
if not currency.is_zero(remainder):
self.raise_user_error('missing_remainder')
return res
PaymentTerm()
class PaymentTermLine(ModelSQL, ModelView):
'Payment Term Line'
_name = 'account.invoice.payment_term.line'
_description = __doc__
__name__ = 'account.invoice.payment_term.line'
sequence = fields.Integer('Sequence',
order_field='(%(table)s.sequence IS NULL) %(order)s, '
'%(table)s.sequence %(order)s',
@@ -111,7 +107,7 @@ class PaymentTermLine(ModelSQL, ModelView):
'required': Eval('type') == 'fixed',
}, depends=['type'])
currency_digits = fields.Function(fields.Integer('Currency Digits',
on_change_with=['currency']), 'get_currency_digits')
on_change_with=['currency']), 'on_change_with_currency_digits')
day = fields.Integer('Day of Month')
month = fields.Selection([
(None, ''),
@@ -142,132 +138,126 @@ class PaymentTermLine(ModelSQL, ModelView):
weeks = fields.Integer('Number of Weeks', required=True)
days = fields.Integer('Number of Days', required=True)
def __init__(self):
super(PaymentTermLine, self).__init__()
self._order.insert(0, ('sequence', 'ASC'))
self._sql_constraints += [
@classmethod
def __setup__(cls):
super(PaymentTermLine, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
cls._sql_constraints += [
('day', 'CHECK(day BETWEEN 1 AND 31)',
'Day of month must be between 1 and 31.'),
]
self._constraints += [
cls._constraints += [
('check_percentage_and_divisor', 'invalid_percentage_and_divisor'),
]
self._error_messages.update({
cls._error_messages.update({
'invalid_percentage_and_divisor': 'Percentage and '
'Divisor values are not consistent.',
})
def init(self, module_name):
super(PaymentTermLine, self).init(module_name)
@classmethod
def __register__(cls, module_name):
super(PaymentTermLine, cls).__register__(module_name)
cursor = Transaction().cursor
table = TableHandler(cursor, self, module_name)
table = TableHandler(cursor, cls, module_name)
# Migration from 1.0 percent change into percentage
if table.column_exist('percent'):
cursor.execute('UPDATE "' + self._table + '" ' \
cursor.execute('UPDATE "' + cls._table + '" ' \
'SET percentage = percent * 100')
table.drop_column('percent', exception=True)
# Migration from 2.2
if table.column_exist('delay'):
cursor.execute('UPDATE "' + self._table + '" SET day = 31 '
cursor.execute('UPDATE "' + cls._table + '" SET day = 31 '
"WHERE delay = 'end_month'")
table.drop_column('delay', exception=True)
ids = self.search([])
for line in self.browse(ids):
lines = cls.search([])
for line in lines:
if line.percentage:
self.write(line.id, {
'divisor': self.round(Decimal('100.0') /
line.percentage, self.divisor.digits[1]),
cls.write([line], {
'divisor': cls.round(Decimal('100.0') /
line.percentage, cls.divisor.digits[1]),
})
# Migration from 2.4: drop required on sequence
table.not_null_action('sequence', action='remove')
def default_currency_digits(self):
@staticmethod
def default_currency_digits():
return 2
def default_type(self):
@staticmethod
def default_type():
return 'remainder'
def default_months(self):
@staticmethod
def default_months():
return 0
def default_weeks(self):
@staticmethod
def default_weeks():
return 0
def default_days(self):
@staticmethod
def default_days():
return 0
def on_change_type(self, vals):
if not 'type' in vals:
return {}
def on_change_type(self):
res = {}
if vals['type'] != 'fixed':
if self.type != 'fixed':
res['amount'] = Decimal('0.0')
res['currency'] = None
if vals['type'] not in ('percent', 'percent_on_total'):
if self.type not in ('percent', 'percent_on_total'):
res['percentage'] = Decimal('0.0')
res['divisor'] = Decimal('0.0')
return res
def on_change_percentage(self, value):
if not value.get('percentage'):
def on_change_percentage(self):
if not self.percentage:
return {'divisor': 0.0}
return {
'divisor': self.round(Decimal('100.0') / value['percentage'],
self.divisor.digits[1]),
'divisor': self.round(Decimal('100.0') / self.percentage,
self.__class__.divisor.digits[1]),
}
def on_change_divisor(self, value):
if not value.get('divisor'):
def on_change_divisor(self):
if not self.divisor:
return {'percentage': 0.0}
return {
'percentage': self.round(Decimal('100.0') / value['divisor'],
self.percentage.digits[1]),
'percentage': self.round(Decimal('100.0') / self.divisor,
self.__class__.percentage.digits[1]),
}
def on_change_with_currency_digits(self, vals):
currency_obj = Pool().get('currency.currency')
if vals.get('currency'):
currency = currency_obj.browse(vals['currency'])
return currency.digits
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
return 2
def get_currency_digits(self, ids, name):
res = {}
for line in self.browse(ids):
if line.currency:
res[line.id] = line.currency.digits
else:
res[line.id] = 2
return res
def get_delta(self, line):
def get_delta(self):
return {
'day': line.day,
'month': int(line.month) if line.month else None,
'days': line.days,
'weeks': line.weeks,
'months': line.months,
'weekday': int(line.weekday) if line.weekday else None,
'day': self.day,
'month': int(self.month) if self.month else None,
'days': self.days,
'weeks': self.weeks,
'months': self.months,
'weekday': int(self.weekday) if self.weekday else None,
}
def get_date(self, line, date):
return date + relativedelta(**self.get_delta(line))
def get_date(self, date):
return date + relativedelta(**self.get_delta())
def get_value(self, line, remainder, amount, currency):
currency_obj = Pool().get('currency.currency')
if line.type == 'fixed':
return currency_obj.compute(line.currency, line.amount, currency)
elif line.type == 'percent':
return currency_obj.round(currency,
remainder * line.percentage / Decimal('100'))
elif line.type == 'percent_on_total':
return currency_obj.round(currency,
amount * line.percentage / Decimal('100'))
elif line.type == 'remainder':
return currency_obj.round(currency, remainder)
def get_value(self, remainder, amount, currency):
Currency = Pool().get('currency.currency')
if self.type == 'fixed':
return Currency.compute(self.currency, self.amount, currency)
elif self.type == 'percent':
return currency.round(
remainder * self.percentage / Decimal('100'))
elif self.type == 'percent_on_total':
return currency.round(
amount * self.percentage / Decimal('100'))
elif self.type == 'remainder':
return currency.round(remainder)
return None
@staticmethod
@@ -275,11 +265,12 @@ class PaymentTermLine(ModelSQL, ModelView):
quantize = Decimal(10) ** -Decimal(digits)
return Decimal(number).quantize(quantize)
def check_percentage_and_divisor(self, ids):
@classmethod
def check_percentage_and_divisor(cls, lines):
"Check consistency between percentage and divisor"
percentage_digits = self.percentage.digits[1]
divisor_digits = self.divisor.digits[1]
for line in self.browse(ids):
percentage_digits = cls.percentage.digits[1]
divisor_digits = cls.divisor.digits[1]
for line in lines:
if line.type not in ('percent', 'percent_on_total'):
continue
if line.percentage is None or line.divisor is None:
@@ -288,14 +279,12 @@ class PaymentTermLine(ModelSQL, ModelView):
continue
percentage = line.percentage
divisor = line.divisor
calc_percentage = self.round(Decimal('100.0') / divisor,
calc_percentage = cls.round(Decimal('100.0') / divisor,
percentage_digits)
calc_divisor = self.round(Decimal('100.0') / percentage,
calc_divisor = cls.round(Decimal('100.0') / percentage,
divisor_digits)
if (percentage == Decimal('0.0') or divisor == Decimal('0.0')
or percentage != calc_percentage
and divisor != calc_divisor):
return False
return True
PaymentTermLine()
+5 -7
View File
@@ -45,14 +45,13 @@ class AccountInvoiceTestCase(unittest.TestCase):
Test payment_term.
'''
with Transaction().start(DB_NAME, USER, context=CONTEXT):
cu1_id = self.currency.create({
cu1 = self.currency.create({
'name': 'cu1',
'symbol': 'cu1',
'code': 'cu1'
})
cu1 = self.currency.browse(cu1_id)
term_id = self.payment_term.create({
term = self.payment_term.create({
'name': '30 days, 1 month, 1 month + 15 days',
'lines': [
('create', {
@@ -75,7 +74,7 @@ class AccountInvoiceTestCase(unittest.TestCase):
'months': 1,
'days': 30,
'amount': Decimal('396.84'),
'currency': cu1_id,
'currency': cu1.id,
}),
('create', {
'sequence': 3,
@@ -85,9 +84,8 @@ class AccountInvoiceTestCase(unittest.TestCase):
'day': 15,
})]
})
term = self.payment_term.browse(term_id)
terms = term.compute(Decimal('1587.35'), cu1, term,
date=datetime.date(2011, 10, 1))
terms = term.compute(Decimal('1587.35'), cu1,
date=datetime.date(2011, 10, 1))
self.assertEqual(terms, [
(datetime.date(2011, 10, 31), Decimal('396.84')),
(datetime.date(2011, 11, 01), Decimal('396.84')),