[RFR] Split off payment functionality in a different addon

This commit is contained in:
Stefan Rijnhart
2013-03-16 17:44:19 +01:00
parent b27825b7a8
commit 8f9fada351
29 changed files with 1331 additions and 945 deletions

View File

@@ -43,7 +43,6 @@
'data/account_banking_data.xml',
'wizard/bank_import_view.xml',
'account_banking_view.xml',
'account_banking_workflow.xml',
'wizard/banking_transaction_wizard.xml',
'workflow/account_invoice.xml',
],

View File

@@ -272,68 +272,6 @@ class account_banking_imported_file(osv.osv):
}
account_banking_imported_file()
class payment_mode_type(osv.osv):
_name= 'payment.mode.type'
_description= 'Payment Mode Type'
_columns= {
'name': fields.char(
'Name', size=64, required=True,
help='Payment Type'
),
'code': fields.char(
'Code', size=64, required=True,
help='Specify the Code for Payment Type'
),
# Setting suitable_bank_types to required pending
# https://bugs.launchpad.net/openobject-addons/+bug/786845
'suitable_bank_types': fields.many2many(
'res.partner.bank.type',
'bank_type_payment_type_rel',
'pay_type_id','bank_type_id',
'Suitable bank types', required=True),
'ir_model_id': fields.many2one(
'ir.model', 'Payment wizard',
help=('Select the Payment Wizard for payments of this type. '
'Leave empty for manual processing'),
domain=[('osv_memory', '=', True)],
),
'payment_order_type': fields.selection(
[('payment', 'Payment'),('debit', 'Direct debit')],
'Payment order type', required=True,
),
}
_defaults = {
'payment_order_type': lambda *a: 'payment',
}
payment_mode_type()
class payment_mode(osv.osv):
''' Restoring the payment type from version 5,
used to select the export wizard (if any) '''
_inherit = "payment.mode"
def suitable_bank_types(self, cr, uid, payment_mode_id=None, context=None):
""" Reinstates functional code for suitable bank type filtering.
Current code in account_payment is disfunctional.
"""
res = []
payment_mode = self.browse(
cr, uid, payment_mode_id, context)
if (payment_mode and payment_mode.type and
payment_mode.type.suitable_bank_types):
res = [type.code for type in payment_mode.type.suitable_bank_types]
return res
_columns = {
'type': fields.many2one(
'payment.mode.type', 'Payment type',
help='Select the Payment Type for the Payment Mode.'
),
}
payment_mode()
class account_bank_statement(osv.osv):
'''
Extensions from account_bank_statement:
@@ -785,419 +723,6 @@ class account_bank_statement_line(osv.osv):
account_bank_statement_line()
class payment_line(osv.osv):
'''
Add extra export_state and date_done fields; make destination bank account
mandatory, as it makes no sense to send payments into thin air.
Edit: Payments can be by cash too, which is prohibited by mandatory bank
accounts.
'''
_inherit = 'payment.line'
_columns = {
# New fields
'export_state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('rejected', 'Rejected'),
('done','Done'),
], 'State', select=True
),
'msg': fields.char('Message', size=255, required=False, readonly=True),
# Redefined fields: added states
'date_done': fields.datetime('Date Confirmed', select=True,
readonly=True),
'name': fields.char(
'Your Reference', size=64, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication': fields.char(
'Communication', size=64, required=False,
help=("Used as the message between ordering customer and current "
"company. Depicts 'What do you want to say to the recipient"
" about this order ?'"
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication2': fields.char(
'Communication 2', size=128,
help='The successor message of Communication.',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'move_line_id': fields.many2one(
'account.move.line', 'Entry line',
domain=[('reconcile_id','=', False),
('account_id.type', '=','payable')
],
help=('This Entry Line will be referred for the information of '
'the ordering customer.'
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'amount_currency': fields.float(
'Amount in Partner Currency', digits=(16,2),
required=True,
help='Payment amount in the partner currency',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'currency': fields.many2one(
'res.currency', 'Partner Currency', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'bank_id': fields.many2one(
'res.partner.bank', 'Destination Bank account',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'order_id': fields.many2one(
'payment.order', 'Order', required=True,
ondelete='cascade', select=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'partner_id': fields.many2one(
'res.partner', string="Partner", required=True,
help='The Ordering Customer',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date': fields.date(
'Payment Date',
help=("If no payment date is specified, the bank will treat this "
"payment line directly"
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'state': fields.selection([
('normal','Free'),
('structured','Structured')
], 'Communication Type', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
}
_defaults = {
'export_state': lambda *a: 'draft',
'date_done': lambda *a: False,
'msg': lambda *a: '',
}
def fields_get(self, cr, uid, fields=None, context=None):
res = super(payment_line, self).fields_get(cr, uid, fields, context)
if 'communication' in res:
res['communication'].setdefault('states', {})
res['communication']['states']['structured'] = [('required', True)]
if 'communication2' in res:
res['communication2'].setdefault('states', {})
res['communication2']['states']['structured'] = [('readonly', True)]
res['communication2']['states']['normal'] = [('readonly', False)]
return res
"""
Hooks for processing direct debit orders, such as implemented in
account_direct_debit module.
"""
def get_storno_account_id(self, cr, uid, payment_line_id, amount,
currency_id, context=None):
"""
Hook for verifying a match of the payment line with the amount.
Return the account associated with the storno.
Used in account_banking interactive mode
:param payment_line_id: the single payment line id
:param amount: the (signed) amount debited from the bank account
:param currency: the bank account's currency *browse object*
:return: an account if there is a full match, False otherwise
:rtype: database id of an account.account resource.
"""
return False
def debit_storno(self, cr, uid, payment_line_id, amount,
currency_id, storno_retry=True, context=None):
"""
Hook for handling a canceled item of a direct debit order.
Presumably called from a bank statement import routine.
Decide on the direction that the invoice's workflow needs to take.
You may optionally return an incomplete reconcile for the caller
to reconcile the now void payment.
:param payment_line_id: the single payment line id
:param amount: the (negative) amount debited from the bank account
:param currency: the bank account's currency *browse object*
:param boolean storno_retry: whether the storno is considered fatal \
or not.
:return: an incomplete reconcile for the caller to fill
:rtype: database id of an account.move.reconcile resource.
"""
return False
payment_line()
class payment_order(osv.osv):
'''
Enable extra states for payment exports
'''
_inherit = 'payment.order'
_columns = {
'date_scheduled': fields.date(
'Scheduled date if fixed',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select a date if you have chosen Preferred Date to be fixed.'
),
'reference': fields.char(
'Reference', size=128, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'mode': fields.many2one(
'payment.mode', 'Payment mode', select=True, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select the Payment Mode to be applied.',
),
'state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('rejected', 'Rejected'),
('done','Done'),
], 'State', select=True
),
'line_ids': fields.one2many(
'payment.line', 'order_id', 'Payment lines',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'user_id': fields.many2one(
'res.users','User', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date_prefered': fields.selection([
('now', 'Directly'),
('due', 'Due date'),
('fixed', 'Fixed date')
], "Preferred date", change_default=True, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help=("Choose an option for the Payment Order:'Fixed' stands for a "
"date specified by you.'Directly' stands for the direct "
"execution.'Due date' stands for the scheduled date of "
"execution."
)
),
'payment_order_type': fields.selection(
[('payment', 'Payment'),('debit', 'Direct debit')],
'Payment order type', required=True,
),
'date_sent': fields.date('Send date', readonly=True),
}
_defaults = {
'payment_order_type': lambda *a: 'payment',
}
def launch_wizard(self, cr, uid, ids, context=None):
"""
Search for a wizard to launch according to the type.
If type is manual. just confirm the order.
Previously (pre-v6) in account_payment/wizard/wizard_pay.py
"""
if context == None:
context = {}
result = {}
orders = self.browse(cr, uid, ids, context)
order = orders[0]
# check if a wizard is defined for the first order
if order.mode.type and order.mode.type.ir_model_id:
context['active_ids'] = ids
wizard_model = order.mode.type.ir_model_id.model
wizard_obj = self.pool.get(wizard_model)
wizard_id = wizard_obj.create(cr, uid, {}, context)
result = {
'name': wizard_obj._description or 'Payment Order Export',
'view_type': 'form',
'view_mode': 'form',
'res_model': wizard_model,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': wizard_id,
'nodestroy': True,
}
else:
# should all be manual orders without type or wizard model
for order in orders[1:]:
if order.mode.type and order.mode.type.ir_model_id:
raise osv.except_osv(
_('Error'),
_('You can only combine payment orders of the same type')
)
# process manual payments
wf_service = netsvc.LocalService('workflow')
for order_id in ids:
wf_service.trg_validate(uid, 'payment.order', order_id, 'sent', cr)
return result
def _write_payment_lines(self, cursor, uid, ids, **kwargs):
'''
ORM method for setting attributes of corresponding payment.line objects.
Note that while this is ORM compliant, it is also very ineffecient due
to the absence of filters on writes and hence the requirement to
filter on the client(=OpenERP server) side.
'''
if not hasattr(ids, '__iter__'):
ids = [ids]
payment_line_obj = self.pool.get('payment.line')
line_ids = payment_line_obj.search(
cursor, uid, [
('order_id', 'in', ids)
])
payment_line_obj.write(cursor, uid, line_ids, kwargs)
def set_to_draft(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'draft'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='draft')
return super(payment_order, self).set_to_draft(
cursor, uid, ids, *args
)
def action_sent(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'sent'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='sent')
self.write(cursor, uid, ids, {'state':'sent',
'date_sent': time.strftime('%Y-%m-%d')})
return True
def action_rejected(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'rejected'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='rejected')
wf_service = netsvc.LocalService('workflow')
for id in ids:
wf_service.trg_validate(uid, 'payment.order', id, 'rejected', cursor)
return True
def set_done(self, cursor, uid, ids, *args):
'''
Extend standard transition to update children as well.
'''
self._write_payment_lines(cursor, uid, ids,
export_state='done',
date_done=time.strftime('%Y-%m-%d')
)
return super(payment_order, self).set_done(
cursor, uid, ids, *args
)
def get_wizard(self, type):
'''
Intercept manual bank payments to include 'sent' state. Default
'manual' payments are flagged 'done' immediately.
'''
if type == 'BANKMAN':
# Note that self._module gets overwritten by inheriters, so make
# the module name hard coded.
return 'account_banking', 'wizard_account_banking_payment_manual'
return super(payment_order, self).get_wizard(type)
"""
Hooks for processing direct debit orders, such as implemented in
account_direct_debit module.
"""
def debit_reconcile_transfer(
self, cr, uid, payment_order_id, amount, currency, context=None):
"""
Reconcile the payment order if the amount is correct. Return the
id of the reconciliation.
"""
raise osv.except_osv(
_("Cannot reconcile"),
_("Cannot reconcile debit order: "+
"Not implemented."))
def debit_unreconcile_transfer(
self, cr, uid, payment_order_id, reconcile_id, amount, currency,
context=None):
""" Unreconcile the payment_order if at all possible """
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile debit order: "+
"Not implemented."))
payment_order()
class res_partner_bank(osv.osv):
'''

View File

@@ -330,45 +330,6 @@
</field>
</record>
<!-- Make buttons on payment order sensitive for extra states,
restore wizard functionality when making payments
-->
<record id="view_banking_payment_order_form_1" model="ir.ui.view">
<field name="name">account.payment.order.form.banking-1</field>
<field name="inherit_id" ref="account_payment.view_payment_order_form" />
<field name="model">payment.order</field>
<field name="type">form</field>
<field name="arch" type="xml">
<data>
<xpath expr="/form/group/button[@string='Select Invoices to Pay']"
position="replace">
<button colspan="2" name="%(account_payment.action_create_payment_order)s"
string="Select Invoices to Pay" type="action"
attrs="{'invisible':[('state','!=','draft')]}"
icon="gtk-find"
/>
</xpath>
<xpath expr="/form/group/button[@string='Make Payments']"
position="replace">
<button name="launch_wizard" states="open" string="Make Payments" type="object" icon="gtk-execute"/>
<newline/>
</xpath>
</data>
</field>
</record>
<record id="view_banking_payment_order_tree_1" model="ir.ui.view">
<field name="name">account.payment.order.tree.banking-1</field>
<field name="inherit_id" ref="account_payment.view_payment_order_tree" />
<field name="model">payment.order</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<button string="Make Payments" position="replace">
<button name="launch_wizard" states="open" string="Make Payments" type="object" icon="gtk-execute"/>
</button>
</field>
</record>
<!-- Set trigger on IBAN and acc_number fields in res_partner_bank form -->
<!--record id="view_partner_bank_account_banking_form_1" model="ir.ui.view">
<field name="name">res.partner.bank.form.banking-1</field>
@@ -459,54 +420,6 @@
</field>
</record>
<!-- Insert payment_mode.type -->
<record id="view_payment_mode_form_inherit" model="ir.ui.view">
<field name="name">payment.mode.form.inherit</field>
<field name="model">payment.mode</field>
<field name="inherit_id" ref="account_payment.view_payment_mode_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="type"/>
</field>
</field>
</record>
<record id="view_payment_mode_tree_inherit" model="ir.ui.view">
<field name="name">payment.mode.tree.inherit</field>
<field name="model">payment.mode</field>
<field name="inherit_id" ref="account_payment.view_payment_mode_tree"/>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="type"/>
</field>
</field>
</record>
<!-- basic view for payment mode type -->
<record model="ir.ui.view" id="view_payment_mode_type_form">
<field name="name">view.payment.mode.type.form</field>
<field name="model">payment.mode.type</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form>
<field name="name" />
<field name="code" />
<field name="suitable_bank_types"/>
<field name="payment_order_type"/>
<field name="ir_model_id"/>
</form>
</field>
</record>
<!-- fixes https://bugs.launchpad.net/openobject-addons/+bug/903156 for 6.0
Note that 6.1 does not suffer from the problem
-->
<record id="account_payment.action_create_payment_order" model="ir.actions.act_window">
<field name="view_id" ref="account_payment.view_create_payment_order"/>
</record>
<record model="ir.ui.view" id="view_bank_statement_line_tree">
<field name="name">Bank statement line tree view</field>
<field name="model">account.bank.statement.line</field>

View File

@@ -123,43 +123,6 @@ class banking_import_transaction(osv.osv):
# return move_lines to mix with the rest
return [x for x in invoice.move_id.line_id if x.account_id.reconcile]
def _match_debit_order(
self, cr, uid, trans, log, context=None):
def is_zero(total):
return self.pool.get('res.currency').is_zero(
cr, uid, trans.statement_id.currency, total)
payment_order_obj = self.pool.get('payment.order')
order_ids = payment_order_obj.search(
cr, uid, [('payment_order_type', '=', 'debit'),
('state', '=', 'sent'),
('date_sent', '<=', str2date(trans.execution_date,
'%Y-%m-%d'))
],
limit=0, context=context)
orders = payment_order_obj.browse(cr, uid, order_ids, context)
candidates = [x for x in orders if
is_zero(x.total - trans.transferred_amount)]
if len(candidates) > 0:
# retrieve the common account_id, if any
account_id = False
for line in candidates[0].line_ids[0].debit_move_line_id.move_id.line_id:
if line.account_id.type == 'other':
account_id = line.account_id.id
break
return dict(
move_line_ids = False,
match_type = 'payment_order',
payment_order_ids = [x.id for x in candidates],
account_id = account_id,
partner_id = False,
partner_bank_id = False,
reference = False,
type='general',
)
return False
def _match_invoice(self, cr, uid, trans, move_lines,
partner_ids, bank_account_ids,
log, linked_invoices,
@@ -542,101 +505,6 @@ class banking_import_transaction(osv.osv):
{'voucher_id': voucher_id}, context=context)
transaction.refresh()
def _confirm_storno(
self, cr, uid, transaction_id, context=None):
"""
Creation of the reconciliation has been delegated to
*a* direct debit module, to allow for various direct debit styles
"""
payment_line_pool = self.pool.get('payment.line')
statement_line_pool = self.pool.get('account.bank.statement.line')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
raise osv.except_osv(
_("Cannot link with storno"),
_("No direct debit order item"))
reconcile_id = payment_line_pool.debit_storno(
cr, uid,
transaction.payment_line_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency,
transaction.storno_retry,
context=context)
statement_line_pool.write(
cr, uid, transaction.statement_line_id.id,
{'reconcile_id': reconcile_id}, context=context)
transaction.refresh()
def _confirm_payment_order(
self, cr, uid, transaction_id, context=None):
"""
Creation of the reconciliation has been delegated to
*a* direct debit module, to allow for various direct debit styles
"""
payment_order_obj = self.pool.get('payment.order')
statement_line_pool = self.pool.get('account.bank.statement.line')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot reconcile"),
_("Cannot reconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot reconcile"),
_("Reconcile payment order not implemented"))
reconcile_id = payment_order_obj.debit_reconcile_transfer(
cr, uid,
transaction.payment_order_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency,
context=context)
statement_line_pool.write(
cr, uid, transaction.statement_line_id.id,
{'reconcile_id': reconcile_id}, context=context)
def _confirm_payment(
self, cr, uid, transaction_id, context=None):
"""
Do some housekeeping on the payment line
then pass on to _reconcile_move
"""
transaction = self.browse(cr, uid, transaction_id, context=context)
payment_line_obj = self.pool.get('payment.line')
payment_line_obj.write(
cr, uid, transaction.payment_line_id.id, {
'export_state': 'done',
'date_done': transaction.statement_line_id.date,
}
)
self._confirm_move(cr, uid, transaction_id, context=context)
def _cancel_payment(
self, cr, uid, transaction_id, context=None):
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile: this operation is not yet supported for "
"match type 'payment'"))
def _cancel_payment_order(
self, cr, uid, transaction_id, context=None):
"""
"""
payment_order_obj = self.pool.get('payment.order')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot unreconcile"),
_("Unreconcile payment order not implemented"))
return payment_order_obj.debit_unreconcile_transfer(
cr, uid, transaction.payment_order_id.id,
transaction.statement_line_id.reconcile_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency)
def _legacy_do_move_unreconcile(self, cr, uid, move_line_ids, currency, context=None):
"""
Legacy method. Allow for canceling bank statement lines that
@@ -768,70 +636,10 @@ class banking_import_transaction(osv.osv):
return True
def _cancel_storno(
self, cr, uid, transaction_id, context=None):
"""
TODO: delegate unreconciliation to the direct debit module,
to allow for various direct debit styles
"""
payment_line_obj = self.pool.get('payment.line')
reconcile_obj = self.pool.get('account.move.reconcile')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("No direct debit order item"))
if not transaction.payment_line_id.storno:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("The direct debit order item is not marked for storno"))
journal = transaction.statement_line_id.statement_id.journal_id
if transaction.statement_line_id.amount >= 0:
account_id = journal.default_credit_account_id.id
else:
account_id = journal.default_debit_account_id.id
cancel_line = False
move_lines = []
for move in transaction.statement_line_id.move_ids:
# There should usually be just one move, I think
move_lines += move.line_id
for line in move_lines:
if line.account_id.id != account_id:
cancel_line = line
break
if not cancel_line:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("Line id not found"))
reconcile = cancel_line.reconcile_id or cancel_line.reconcile_partial_id
lines_reconcile = reconcile.line_id or reconcile.line_partial_ids
if len(lines_reconcile) < 3:
# delete the full reconciliation
reconcile_obj.unlink(cr, uid, reconcile.id, context)
else:
# we are left with a partial reconciliation
reconcile_obj.write(
cr, uid, reconcile.id,
{'line_partial_ids':
[(6, 0, [x.id for x in lines_reconcile if x.id != cancel_line.id])],
'line_id': [(6, 0, [])],
}, context)
# redo the original payment line reconciliation with the invoice
payment_line_obj.write(
cr, uid, transaction.payment_line_id.id,
{'storno': False}, context)
payment_line_obj.debit_reconcile(
cr, uid, transaction.payment_line_id.id, context)
cancel_map = {
'storno': _cancel_storno,
'invoice': _cancel_voucher,
'manual': _cancel_voucher,
'move': _cancel_voucher,
'payment_order': _cancel_payment_order,
'payment': _cancel_payment,
}
def cancel(self, cr, uid, ids, context=None):
@@ -850,11 +658,8 @@ class banking_import_transaction(osv.osv):
return True
confirm_map = {
'storno': _confirm_storno,
'invoice': _confirm_move,
'manual': _confirm_move,
'payment_order': _confirm_payment_order,
'payment': _confirm_payment,
'move': _confirm_move,
}
@@ -892,66 +697,6 @@ class banking_import_transaction(osv.osv):
"""
return True
def _match_storno(
self, cr, uid, trans, log, context=None):
payment_line_obj = self.pool.get('payment.line')
line_ids = payment_line_obj.search(
cr, uid, [
('order_id.payment_order_type', '=', 'debit'),
('order_id.state', 'in', ['sent', 'done']),
('communication', '=', trans.reference)
], context=context)
# stornos MUST have an exact match
if len(line_ids) == 1:
account_id = payment_line_obj.get_storno_account_id(
cr, uid, line_ids[0], trans.transferred_amount,
trans.statement_id.currency, context=None)
if account_id:
return dict(
account_id = account_id,
match_type = 'storno',
payment_line_id = line_ids[0],
move_line_ids=False,
partner_id=False,
partner_bank_id=False,
reference=False,
type='customer',
)
# TODO log the reason why there is no result for transfers marked
# as storno
return False
def _match_payment(self, cr, uid, trans, payment_lines,
partner_ids, bank_account_ids, log, linked_payments):
'''
Find the payment order belonging to this reference - if there is one
This is the easiest part: when sending payments, the returned bank info
should be identical to ours.
This also means that we do not allow for multiple candidates.
'''
# TODO: Not sure what side effects are created when payments are done
# for credited customer invoices, which will be matched later on too.
digits = dp.get_precision('Account')(cr)[1]
candidates = [x for x in payment_lines
if x.communication == trans.reference
and round(x.amount, digits) == -round(trans.transferred_amount, digits)
and trans.remote_account in (x.bank_id.acc_number,
x.bank_id.acc_number_domestic)
]
if len(candidates) == 1:
candidate = candidates[0]
# Check cache to prevent multiple matching of a single payment
if candidate.id not in linked_payments:
linked_payments[candidate.id] = True
move_info = self._get_move_info(cr, uid, [candidate.move_line_id.id])
move_info.update({
'match_type': 'payment',
'payment_line_id': candidate.id,
})
return move_info
return False
signal_duplicate_keys = [
# does not include float values
# such as transferred_amount
@@ -1059,6 +804,22 @@ class banking_import_transaction(osv.osv):
retval['invoice_ids'] = [x.invoice.id for x in move_lines]
retval['type'] = type_map[move_lines[0].invoice.type]
return retval
def move_info2values(move_info):
vals = {}
vals['match_type'] = move_info['match_type']
vals['move_line_ids'] = [(6, 0, move_info.get('move_line_ids') or [])]
vals['invoice_ids'] = [(6, 0, move_info.get('invoice_ids') or [])]
vals['move_line_id'] = (move_info.get('move_line_ids', False) and
len(move_info['move_line_ids']) == 1 and
move_info['move_line_ids'][0]
)
if move_info['match_type'] == 'invoice':
vals['invoice_id'] = (move_info.get('invoice_ids', False) and
len(move_info['invoice_ids']) == 1 and
move_info['invoice_ids'][0]
)
return vals
def match(self, cr, uid, ids, results=None, context=None):
if not ids:
@@ -1069,6 +830,7 @@ class banking_import_transaction(osv.osv):
journal_obj = self.pool.get('account.journal')
move_line_obj = self.pool.get('account.move.line')
payment_line_obj = self.pool.get('payment.line')
has_payment = bool(payment_line_obj)
statement_line_obj = self.pool.get('account.bank.statement.line')
statement_obj = self.pool.get('account.bank.statement')
payment_order_obj = self.pool.get('payment.order')
@@ -1098,14 +860,15 @@ class banking_import_transaction(osv.osv):
# communication. Most likely there are much less sent payments
# than reconciled and open/draft payments.
# Strangely, payment_orders still do not have company_id
cr.execute("SELECT l.id FROM payment_order o, payment_line l "
"WHERE l.order_id = o.id AND "
"o.state = 'sent' AND "
"l.date_done IS NULL"
)
payment_line_ids = [x[0] for x in cr.fetchall()]
if payment_line_ids:
payment_lines = payment_line_obj.browse(cr, uid, payment_line_ids)
if has_payment:
payment_line_ids = payment_line_obj.search(
cr, uid, [
('order_id.state', '=', 'sent'),
('date_done', '=', False)], context=context)
payment_lines = payment_line_obj.browse(
cr, uid, payment_line_ids)
else:
payment_lines = False
# Start the loop over the transactions requested to match
transactions = self.browse(cr, uid, ids, context)
@@ -1270,10 +1033,10 @@ class banking_import_transaction(osv.osv):
# rebrowse the current record after writing
transaction = self.browse(cr, uid, transaction.id, context=context)
# Match full direct debit orders
if transaction.type == bt.DIRECT_DEBIT:
if transaction.type == bt.DIRECT_DEBIT and has_payment:
move_info = self._match_debit_order(
cr, uid, transaction, results['log'], context)
if transaction.type == bt.STORNO:
if transaction.type == bt.STORNO and has_payment:
move_info = self._match_storno(
cr, uid, transaction, results['log'], context)
# Allow inclusion of generated bank invoices
@@ -1340,6 +1103,10 @@ class banking_import_transaction(osv.osv):
if (not move_info
and transaction.transferred_amount < 0 and payment_lines):
# Link open payment - if any
# Note that _match_payment is defined in the
# account_banking_payment module which should be installed
# automatically if account_payment is. And if account_payment
# is not installed, then payment_lines will be empty.
move_info = self._match_payment(
cr, uid, transaction,
payment_lines, partner_ids,
@@ -1385,28 +1152,12 @@ class banking_import_transaction(osv.osv):
self_values = {}
if move_info:
results['trans_matched_cnt'] += 1
self_values['match_type'] = move_info['match_type']
self_values['payment_line_id'] = move_info.get('payment_line_id', False)
self_values['move_line_ids'] = [(6, 0, move_info.get('move_line_ids') or [])]
self_values['invoice_ids'] = [(6, 0, move_info.get('invoice_ids') or [])]
self_values['payment_order_ids'] = [(6, 0, move_info.get('payment_order_ids') or [])]
self_values['payment_order_id'] = (move_info.get('payment_order_ids', False) and
len(move_info['payment_order_ids']) == 1 and
move_info['payment_order_ids'][0]
)
self_values['move_line_id'] = (move_info.get('move_line_ids', False) and
len(move_info['move_line_ids']) == 1 and
move_info['move_line_ids'][0]
)
if move_info['match_type'] == 'invoice':
self_values['invoice_id'] = (move_info.get('invoice_ids', False) and
len(move_info['invoice_ids']) == 1 and
move_info['invoice_ids'][0]
)
self_values.update(
self.move_info2values(move_info))
# values['match_type'] = move_info['match_type']
values['partner_id'] = move_info['partner_id']
values['partner_bank_id'] = move_info['partner_bank_id']
values['type'] = move_info['type']
# values['match_type'] = move_info['match_type']
else:
values['partner_id'] = values['partner_bank_id'] = False
if not values['partner_id'] and partner_ids and len(partner_ids) == 1:
@@ -1446,34 +1197,6 @@ class banking_import_transaction(osv.osv):
statement_obj.button_dummy(
cr, uid, imported_statement_ids, context=context)
if payment_lines:
# As payments lines are treated as individual transactions, the
# batch as a whole is only marked as 'done' when all payment lines
# have been reconciled.
cr.execute(
"SELECT DISTINCT o.id "
"FROM payment_order o, payment_line l "
"WHERE o.state = 'sent' "
"AND o.id = l.order_id "
"AND o.id NOT IN ("
"SELECT DISTINCT order_id AS id "
"FROM payment_line "
"WHERE date_done IS NULL "
"AND id IN (%s)"
")" % (','.join([str(x) for x in payment_line_ids]))
)
order_ids = [x[0] for x in cr.fetchall()]
if order_ids:
# Use workflow logics for the orders. Recode logic from
# account_payment, in order to increase efficiency.
payment_order_obj.set_done(cr, uid, order_ids,
{'state': 'done'}
)
wf_service = netsvc.LocalService('workflow')
for id in order_ids:
wf_service.trg_validate(
uid, 'payment.order', id, 'done', cr)
def _get_residual(self, cr, uid, ids, name, args, context=None):
"""
Calculate the residual against the candidate reconciliation.
@@ -1518,10 +1241,6 @@ class banking_import_transaction(osv.osv):
elif transaction.match_type == 'invoice':
if transaction.invoice_ids and not transaction.invoice_id:
res[transaction.id] = True
elif transaction.match_type == 'payment_order':
if (transaction.payment_order_ids and not
transaction.payment_order_id):
res[transaction.id] = True
return res
def clear_and_write(self, cr, uid, ids, vals=None, context=None):
@@ -1535,12 +1254,10 @@ class banking_import_transaction(osv.osv):
'invoice_id',
'manual_invoice_id',
'manual_move_line_id',
'payment_line_id',
]] +
[(x, [(6, 0, [])]) for x in [
'move_line_ids',
'invoice_ids',
'payment_order_ids',
]]))
write_vals.update(vals or {})
return self.write(cr, uid, ids, write_vals, context=context)
@@ -1645,23 +1362,15 @@ class banking_import_transaction(osv.osv):
# match fields
'match_type': fields.selection(
[('manual', 'Manual'), ('move','Move'), ('invoice', 'Invoice'),
('payment', 'Payment'), ('payment_order', 'Payment order'),
('storno', 'Storno')],
'Match type'),
], 'Match type'),
'match_multi': fields.function(
_get_match_multi, method=True, string='Multi match',
type='boolean'),
'payment_order_ids': fields.many2many(
'payment.order', 'banking_transaction_payment_order_rel',
'order_id', 'transaction_id', 'Payment orders'),
'payment_order_id': fields.many2one(
'payment.order', 'Payment order to reconcile'),
'move_line_ids': fields.many2many(
'account.move.line', 'banking_transaction_move_line_rel',
'move_line_id', 'transaction_id', 'Matching entries'),
'move_line_id': fields.many2one(
'account.move.line', 'Entry to reconcile'),
'payment_line_id': fields.many2one('payment.line', 'Payment line'),
'invoice_ids': fields.many2many(
'account.invoice', 'banking_transaction_invoice_rel',
'invoice_id', 'transaction_id', 'Matching invoices'),
@@ -1722,9 +1431,8 @@ class account_bank_statement_line(osv.osv):
'match_type': fields.related(
'import_transaction_id', 'match_type', type='selection',
selection=[('manual', 'Manual'), ('move','Move'),
('invoice', 'Invoice'), ('payment', 'Payment'),
('payment_order', 'Payment order'),
('storno', 'Storno')],
('invoice', 'Invoice'),
],
string='Match type', readonly=True,),
'state': fields.selection(
[('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State',

View File

@@ -20,12 +20,5 @@
<record id="base_iban.bank_swift_field" model="res.partner.bank.type.field">
<field eval="False" name="required"/>
</record>
<!-- Add manual bank transfer as default payment option -->
<record model="payment.mode.type" id="account_banking.manual_bank_tranfer">
<field name="name">Manual Bank Transfer</field>
<field name="code">BANKMAN</field>
<field name="suitable_bank_types"
eval="[(6,0,[ref('base.bank_normal'),ref('base_iban.bank_iban'),])]" />
</record>
</data>
</openerp>

View File

@@ -2,6 +2,5 @@
"access_account_banking_settings","account.banking.account.settings","model_account_banking_account_settings","account.group_account_manager",1,1,1,1
"access_account_banking_settings_user","account.banking.account.settings user","model_account_banking_account_settings","account.group_account_user",1,0,0,0
"access_account_banking_import","account.bankimport","model_account_banking_imported_file","account.group_account_user",1,1,1,1
"access_payment_mode_type","payment.mode.type","model_payment_mode_type","account_payment.group_account_payment",1,1,1,1
"access_banking_import_transaction","Banking addons - Bank import transaction","model_banking_import_transaction","account.group_account_user",1,1,1,1
"access_banking_transaction_wizard","Banking addons - Transaction wizard","model_banking_transaction_wizard","account.group_account_user",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_banking_settings account.banking.account.settings model_account_banking_account_settings account.group_account_manager 1 1 1 1
3 access_account_banking_settings_user account.banking.account.settings user model_account_banking_account_settings account.group_account_user 1 0 0 0
4 access_account_banking_import account.bankimport model_account_banking_imported_file account.group_account_user 1 1 1 1
access_payment_mode_type payment.mode.type model_payment_mode_type account_payment.group_account_payment 1 1 1 1
5 access_banking_import_transaction Banking addons - Bank import transaction model_banking_import_transaction account.group_account_user 1 1 1 1
6 access_banking_transaction_wizard Banking addons - Transaction wizard model_banking_transaction_wizard account.group_account_user 1 1 1 1

View File

@@ -19,8 +19,6 @@
#
##############################################################################
import bank_import
import bank_payment_manual
import account_payment_order
import banking_transaction_wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -85,13 +85,10 @@ class banking_import_line(osv.osv_memory):
'invoice_ids': fields.many2many(
'account.invoice', 'banking_import_line_invoice_rel',
'line_id', 'invoice_id'),
'payment_order_id': fields.many2one('payment.order', 'Payment order'),
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'),
'transaction_type': fields.selection([
# TODO: payment terminal etc...
('invoice', 'Invoice payment'),
('payment_order_line', 'Payment from a payment order'),
('payment_order', 'Aggregate payment order'),
('storno', 'Canceled debit order'),
('bank_costs', 'Bank costs'),
('unknown', 'Unknown'),

View File

@@ -320,15 +320,6 @@ class banking_transaction_wizard(osv.osv_memory):
'import_transaction_id', 'writeoff_journal_id',
type='many2one', relation='account.journal',
string='Write-off journal'),
'payment_line_id': fields.related(
'import_transaction_id', 'payment_line_id', string="Matching payment or storno",
type='many2one', relation='payment.line', readonly=True),
'payment_order_ids': fields.related(
'import_transaction_id', 'payment_order_ids', string="Matching payment orders",
type='many2many', relation='payment.order'),
'payment_order_id': fields.related(
'import_transaction_id', 'payment_order_id', string="Payment order to reconcile",
type='many2one', relation='payment.order'),
'invoice_ids': fields.related(
'import_transaction_id', 'invoice_ids', string="Matching invoices",
type='many2many', relation='account.invoice'),

View File

@@ -8,7 +8,6 @@
<field name="arch" type="xml">
<form string="Match transaction">
<!-- fields used for form logic -->
<field name="payment_order_ids" invisible="True"/>
<field name="invoice_ids" invisible="True"/>
<field name="move_line_ids" invisible="True"/>
<field name="match_multi" invisible="True"/>
@@ -36,9 +35,6 @@
<separator string="Multiple matches" colspan="2"/>
<label colspan="2" string="Multiple matches were found for this bank transfer. You must pick one of the matches or select a match manually below." />
</group>
<field name='payment_line_id'
attrs="{'invisible': [('match_type', '!=', 'storno'),('match_type', '!=', 'payment')]}"
/>
<group attrs="{'readonly': [('match_multi', '!=', True)]}" col="8">
<!-- show if we have an invoice type match (but the user may need to select from multiple options)
or whenever there is an invoice_id (e.g. in case of a manual match)
@@ -53,10 +49,6 @@
attrs="{'readonly': [('match_multi', '=', False)], 'invisible': [('match_type', '!=', 'move'),('invoice_id', '=', False)]}"
domain="[('id', 'in', move_line_ids[0][2])]"
/>
<field name='payment_order_id'
attrs="{'readonly': [('match_multi', '=', False)], 'invisible': [('match_type', '!=', 'payment_order')]}"
domain="[('id', 'in', payment_order_ids[0][2])]"
/>
<field name='analytic_account_id' />
</group>
<button colspan="1"

View File

@@ -0,0 +1 @@
import model

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Account Banking - Payments',
'version': '0.1.164',
'license': 'AGPL-3',
'author': 'Banking addons community',
'website': 'https://launchpad.net/banking-addons',
'category': 'Banking addons',
'depends': [
'account_banking',
'account_payment',
],
'data': [
'view/account_payment.xml',
'view/payment_mode_type.xml',
'data/payment_mode_type.xml',
'workflow/account_payment.xml',
'security/ir.model.access.csv',
],
'description': '''
This addon adds payment infrastructure to the Banking Addons.
* Extends payments for digital banking:
+ Adapted workflow in payments to reflect banking operations
+ Relies on account_payment mechanics to extend with export generators.
- ClieOp3 (NL) payment and direct debit orders files available as
account_banking_nl_clieop
''',
'auto_install': True,
'installable': False,
}

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class banking_import_line(orm.TransientModel):
_inherit = 'banking.import.line'
_columns = {
'payment_order_id': fields.many2one(
'payment.order', 'Payment order'),
'transaction_type': fields.selection([
# Add payment order related transaction types
('invoice', 'Invoice payment'),
('payment_order_line', 'Payment from a payment order'),
('payment_order', 'Aggregate payment order'),
('storno', 'Canceled debit order'),
('bank_costs', 'Bank costs'),
('unknown', 'Unknown'),
], 'Transaction type'),
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Add manual bank transfer as default payment option -->
<record model="payment.mode.type" id="account_banking.manual_bank_tranfer">
<field name="name">Manual Bank Transfer</field>
<field name="code">BANKMAN</field>
<field name="suitable_bank_types"
eval="[(6,0,[ref('base.bank_normal'),ref('base_iban.bank_iban'),])]" />
</record>
</data>
</openerp>

View File

@@ -0,0 +1,10 @@
import account_payment
import payment_line
import payment_mode
import payment_mode_type
import payment_order_create
import banking_import_transaction
import account_bank_statement_line
import banking_transaction_wizard
import bank_payment_manual

View File

@@ -0,0 +1,15 @@
from openerp.osv import orm, fields
class account_bank_statement_line(osv.osv):
_inherit = 'account.bank.statement.line'
_columns = {
'match_type': fields.related(
# Add payment and storno types
'import_transaction_id', 'match_type', type='selection',
selection=[('manual', 'Manual'), ('move','Move'),
('invoice', 'Invoice'), ('payment', 'Payment'),
('payment_order', 'Payment order'),
('storno', 'Storno')],
string='Match type', readonly=True,),
}

View File

@@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class payment_order(orm.Model):
'''
Enable extra states for payment exports
'''
_inherit = 'payment.order'
_columns = {
'date_scheduled': fields.date(
'Scheduled date if fixed',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select a date if you have chosen Preferred Date to be fixed.'
),
'reference': fields.char(
'Reference', size=128, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'mode': fields.many2one(
'payment.mode', 'Payment mode', select=True, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select the Payment Mode to be applied.',
),
'state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('rejected', 'Rejected'),
('done','Done'),
], 'State', select=True
),
'line_ids': fields.one2many(
'payment.line', 'order_id', 'Payment lines',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'user_id': fields.many2one(
'res.users','User', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date_prefered': fields.selection([
('now', 'Directly'),
('due', 'Due date'),
('fixed', 'Fixed date')
], "Preferred date", change_default=True, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
help=("Choose an option for the Payment Order:'Fixed' stands for a "
"date specified by you.'Directly' stands for the direct "
"execution.'Due date' stands for the scheduled date of "
"execution."
)
),
'payment_order_type': fields.selection(
[('payment', 'Payment'),('debit', 'Direct debit')],
'Payment order type', required=True,
),
'date_sent': fields.date('Send date', readonly=True),
}
_defaults = {
'payment_order_type': lambda *a: 'payment',
}
def launch_wizard(self, cr, uid, ids, context=None):
"""
Search for a wizard to launch according to the type.
If type is manual. just confirm the order.
Previously (pre-v6) in account_payment/wizard/wizard_pay.py
"""
if context == None:
context = {}
result = {}
orders = self.browse(cr, uid, ids, context)
order = orders[0]
# check if a wizard is defined for the first order
if order.mode.type and order.mode.type.ir_model_id:
context['active_ids'] = ids
wizard_model = order.mode.type.ir_model_id.model
wizard_obj = self.pool.get(wizard_model)
wizard_id = wizard_obj.create(cr, uid, {}, context)
result = {
'name': wizard_obj._description or 'Payment Order Export',
'view_type': 'form',
'view_mode': 'form',
'res_model': wizard_model,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': wizard_id,
'nodestroy': True,
}
else:
# should all be manual orders without type or wizard model
for order in orders[1:]:
if order.mode.type and order.mode.type.ir_model_id:
raise osv.except_osv(
_('Error'),
_('You can only combine payment orders of the same type')
)
# process manual payments
wf_service = netsvc.LocalService('workflow')
for order_id in ids:
wf_service.trg_validate(uid, 'payment.order', order_id, 'sent', cr)
return result
def _write_payment_lines(self, cursor, uid, ids, **kwargs):
'''
ORM method for setting attributes of corresponding payment.line objects.
Note that while this is ORM compliant, it is also very ineffecient due
to the absence of filters on writes and hence the requirement to
filter on the client(=OpenERP server) side.
'''
if not hasattr(ids, '__iter__'):
ids = [ids]
payment_line_obj = self.pool.get('payment.line')
line_ids = payment_line_obj.search(
cursor, uid, [
('order_id', 'in', ids)
])
payment_line_obj.write(cursor, uid, line_ids, kwargs)
def set_to_draft(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'draft'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='draft')
return super(payment_order, self).set_to_draft(
cursor, uid, ids, *args
)
def action_sent(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'sent'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='sent')
self.write(cursor, uid, ids, {'state':'sent',
'date_sent': time.strftime('%Y-%m-%d')})
return True
def action_rejected(self, cursor, uid, ids, *args):
'''
Set both self and payment lines to state 'rejected'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='rejected')
wf_service = netsvc.LocalService('workflow')
for id in ids:
wf_service.trg_validate(uid, 'payment.order', id, 'rejected', cursor)
return True
def set_done(self, cursor, uid, ids, *args):
'''
Extend standard transition to update children as well.
'''
self._write_payment_lines(cursor, uid, ids,
export_state='done',
date_done=time.strftime('%Y-%m-%d')
)
return super(payment_order, self).set_done(
cursor, uid, ids, *args
)
def get_wizard(self, type):
'''
Intercept manual bank payments to include 'sent' state. Default
'manual' payments are flagged 'done' immediately.
'''
if type == 'BANKMAN':
# Note that self._module gets overwritten by inheriters, so make
# the module name hard coded.
return 'account_banking', 'wizard_account_banking_payment_manual'
return super(payment_order, self).get_wizard(type)
"""
Hooks for processing direct debit orders, such as implemented in
account_direct_debit module.
"""
def debit_reconcile_transfer(
self, cr, uid, payment_order_id, amount, currency, context=None):
"""
Reconcile the payment order if the amount is correct. Return the
id of the reconciliation.
"""
raise osv.except_osv(
_("Cannot reconcile"),
_("Cannot reconcile debit order: "+
"Not implemented."))
def debit_unreconcile_transfer(
self, cr, uid, payment_order_id, reconcile_id, amount, currency,
context=None):
""" Unreconcile the payment_order if at all possible """
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile debit order: "+
"Not implemented."))

View File

@@ -0,0 +1,385 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
from openerp.addons.account_banking.parsers.convert import str2date
class banking_import_transaction(orm.Model):
_inherit = 'banking.import.transaction'
confirm_map = {
# Add storno and payment types
'storno': _confirm_storno,
'invoice': _confirm_move,
'manual': _confirm_move,
'payment_order': _confirm_payment_order,
'payment': _confirm_payment,
'move': _confirm_move,
}
cancel_map = {
'storno': _cancel_storno,
'invoice': _cancel_voucher,
'manual': _cancel_voucher,
'move': _cancel_voucher,
'payment_order': _cancel_payment_order,
'payment': _cancel_payment,
}
def _match_debit_order(
self, cr, uid, trans, log, context=None):
def is_zero(total):
return self.pool.get('res.currency').is_zero(
cr, uid, trans.statement_id.currency, total)
payment_order_obj = self.pool.get('payment.order')
order_ids = payment_order_obj.search(
cr, uid, [('payment_order_type', '=', 'debit'),
('state', '=', 'sent'),
('date_sent', '<=', str2date(trans.execution_date,
'%Y-%m-%d'))
],
limit=0, context=context)
orders = payment_order_obj.browse(cr, uid, order_ids, context)
candidates = [x for x in orders if
is_zero(x.total - trans.transferred_amount)]
if len(candidates) > 0:
# retrieve the common account_id, if any
account_id = False
for line in candidates[0].line_ids[0].debit_move_line_id.move_id.line_id:
if line.account_id.type == 'other':
account_id = line.account_id.id
break
return dict(
move_line_ids = False,
match_type = 'payment_order',
payment_order_ids = [x.id for x in candidates],
account_id = account_id,
partner_id = False,
partner_bank_id = False,
reference = False,
type='general',
)
return False
def _match_storno(
self, cr, uid, trans, log, context=None):
payment_line_obj = self.pool.get('payment.line')
line_ids = payment_line_obj.search(
cr, uid, [
('order_id.payment_order_type', '=', 'debit'),
('order_id.state', 'in', ['sent', 'done']),
('communication', '=', trans.reference)
], context=context)
# stornos MUST have an exact match
if len(line_ids) == 1:
account_id = payment_line_obj.get_storno_account_id(
cr, uid, line_ids[0], trans.transferred_amount,
trans.statement_id.currency, context=None)
if account_id:
return dict(
account_id = account_id,
match_type = 'storno',
payment_line_id = line_ids[0],
move_line_ids=False,
partner_id=False,
partner_bank_id=False,
reference=False,
type='customer',
)
# TODO log the reason why there is no result for transfers marked
# as storno
return False
def _match_payment(self, cr, uid, trans, payment_lines,
partner_ids, bank_account_ids, log, linked_payments):
'''
Find the payment order belonging to this reference - if there is one
This is the easiest part: when sending payments, the returned bank info
should be identical to ours.
This also means that we do not allow for multiple candidates.
'''
# TODO: Not sure what side effects are created when payments are done
# for credited customer invoices, which will be matched later on too.
digits = dp.get_precision('Account')(cr)[1]
candidates = [x for x in payment_lines
if x.communication == trans.reference
and round(x.amount, digits) == -round(trans.transferred_amount, digits)
and trans.remote_account in (x.bank_id.acc_number,
x.bank_id.acc_number_domestic)
]
if len(candidates) == 1:
candidate = candidates[0]
# Check cache to prevent multiple matching of a single payment
if candidate.id not in linked_payments:
linked_payments[candidate.id] = True
move_info = self._get_move_info(cr, uid, [candidate.move_line_id.id])
move_info.update({
'match_type': 'payment',
'payment_line_id': candidate.id,
})
return move_info
return False
def _confirm_storno(
self, cr, uid, transaction_id, context=None):
"""
Creation of the reconciliation has been delegated to
*a* direct debit module, to allow for various direct debit styles
"""
payment_line_pool = self.pool.get('payment.line')
statement_line_pool = self.pool.get('account.bank.statement.line')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
raise osv.except_osv(
_("Cannot link with storno"),
_("No direct debit order item"))
reconcile_id = payment_line_pool.debit_storno(
cr, uid,
transaction.payment_line_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency,
transaction.storno_retry,
context=context)
statement_line_pool.write(
cr, uid, transaction.statement_line_id.id,
{'reconcile_id': reconcile_id}, context=context)
transaction.refresh()
def _confirm_payment_order(
self, cr, uid, transaction_id, context=None):
"""
Creation of the reconciliation has been delegated to
*a* direct debit module, to allow for various direct debit styles
"""
payment_order_obj = self.pool.get('payment.order')
statement_line_pool = self.pool.get('account.bank.statement.line')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot reconcile"),
_("Cannot reconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot reconcile"),
_("Reconcile payment order not implemented"))
reconcile_id = payment_order_obj.debit_reconcile_transfer(
cr, uid,
transaction.payment_order_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency,
context=context)
statement_line_pool.write(
cr, uid, transaction.statement_line_id.id,
{'reconcile_id': reconcile_id}, context=context)
def _confirm_payment(
self, cr, uid, transaction_id, context=None):
"""
Do some housekeeping on the payment line
then pass on to _reconcile_move
"""
transaction = self.browse(cr, uid, transaction_id, context=context)
payment_line_obj = self.pool.get('payment.line')
payment_line_obj.write(
cr, uid, transaction.payment_line_id.id, {
'export_state': 'done',
'date_done': transaction.statement_line_id.date,
}
)
self._confirm_move(cr, uid, transaction_id, context=context)
def _cancel_payment(
self, cr, uid, transaction_id, context=None):
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile: this operation is not yet supported for "
"match type 'payment'"))
def _cancel_payment_order(
self, cr, uid, transaction_id, context=None):
"""
"""
payment_order_obj = self.pool.get('payment.order')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot unreconcile"),
_("Unreconcile payment order not implemented"))
return payment_order_obj.debit_unreconcile_transfer(
cr, uid, transaction.payment_order_id.id,
transaction.statement_line_id.reconcile_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency)
def _cancel_storno(
self, cr, uid, transaction_id, context=None):
"""
TODO: delegate unreconciliation to the direct debit module,
to allow for various direct debit styles
"""
payment_line_obj = self.pool.get('payment.line')
reconcile_obj = self.pool.get('account.move.reconcile')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("No direct debit order item"))
if not transaction.payment_line_id.storno:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("The direct debit order item is not marked for storno"))
journal = transaction.statement_line_id.statement_id.journal_id
if transaction.statement_line_id.amount >= 0:
account_id = journal.default_credit_account_id.id
else:
account_id = journal.default_debit_account_id.id
cancel_line = False
move_lines = []
for move in transaction.statement_line_id.move_ids:
# There should usually be just one move, I think
move_lines += move.line_id
for line in move_lines:
if line.account_id.id != account_id:
cancel_line = line
break
if not cancel_line:
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("Line id not found"))
reconcile = cancel_line.reconcile_id or cancel_line.reconcile_partial_id
lines_reconcile = reconcile.line_id or reconcile.line_partial_ids
if len(lines_reconcile) < 3:
# delete the full reconciliation
reconcile_obj.unlink(cr, uid, reconcile.id, context)
else:
# we are left with a partial reconciliation
reconcile_obj.write(
cr, uid, reconcile.id,
{'line_partial_ids':
[(6, 0, [x.id for x in lines_reconcile if x.id != cancel_line.id])],
'line_id': [(6, 0, [])],
}, context)
# redo the original payment line reconciliation with the invoice
payment_line_obj.write(
cr, uid, transaction.payment_line_id.id,
{'storno': False}, context)
payment_line_obj.debit_reconcile(
cr, uid, transaction.payment_line_id.id, context)
_columns = {
'match_type': fields.selection(
# Add payment and storno types
[
('manual', 'Manual'),
('move','Move'),
('invoice', 'Invoice'),
('payment', 'Payment'),
('payment_order', 'Payment order'),
('storno', 'Storno'),
],
'Match type'),
'payment_order_ids': fields.many2many(
'payment.order', 'banking_transaction_payment_order_rel',
'order_id', 'transaction_id', 'Payment orders'),
'payment_order_id': fields.many2one(
'payment.order', 'Payment order to reconcile'),
'payment_line_id': fields.many2one('payment.line', 'Payment line'),
}
def _get_match_multi(self, cr, uid, ids, name, args, context=None):
if not ids:
return {}
res = super(banking_import_transaction, self)._get_match_multi(
cr, uid, ids, name, args, context=context)
for transaction in self.browse(cr, uid, ids, context):
if transaction.match_type == 'payment_order':
if (transaction.payment_order_ids and not
transaction.payment_order_id):
res[transaction.id] = True
return res
def clear_and_write(self, cr, uid, ids, vals=None, context=None):
super(banking_import_transaction, self).clear_and_write(
cr, uid, ids, vals=vals, context=context)
return self.write(
cr, uid, ids, {
'payment_line_id': False,
'payment_order_ids': [(6, 0, [])],
},
context=context)
def move_info2values(move_info):
vals = super(banking_import_transaction, self).move_info2values
vals['payment_line_id'] = move_info.get('payment_line_id', False)
vals['payment_order_ids'] = [
(6, 0, move_info.get('payment_order_ids') or [])]
vals['payment_order_id'] = (
move_info.get('payment_order_ids', False) and
len(move_info['payment_order_ids']) == 1 and
move_info['payment_order_ids'][0]
)
return vals
def match(self, cr, uid, ids, results=None, context=None):
res = super(banking_import_transaction, self).match(
cr, uid, ids, results=results, context=context)
# As payments lines are treated as individual transactions, the
# batch as a whole is only marked as 'done' when all payment lines
# have been reconciled.
cr.execute(
"SELECT DISTINCT o.id "
"FROM payment_order o, payment_line l "
"WHERE o.state = 'sent' "
"AND o.id = l.order_id "
"AND o.id NOT IN ("
"SELECT DISTINCT order_id AS id "
"FROM payment_line "
"WHERE date_done IS NULL "
"AND id IN (%s)"
")" % (','.join([str(x) for x in payment_line_ids]))
)
order_ids = [x[0] for x in cr.fetchall()]
if order_ids:
# Use workflow logics for the orders. Recode logic from
# account_payment, in order to increase efficiency.
payment_order_obj.set_done(
cr, uid, order_ids, {'state': 'done'})
wf_service = netsvc.LocalService('workflow')
for id in order_ids:
wf_service.trg_validate(
uid, 'payment.order', id, 'done', cr)
return res

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class banking_transaction_wizard(orm.TransientModel):
_inherit = 'banking.transaction.wizard'
_columns = {
'payment_line_id': fields.related(
'import_transaction_id', 'payment_line_id', string="Matching payment or storno",
type='many2one', relation='payment.line', readonly=True),
'payment_order_ids': fields.related(
'import_transaction_id', 'payment_order_ids', string="Matching payment orders",
type='many2many', relation='payment.order'),
'payment_order_id': fields.related(
'import_transaction_id', 'payment_order_id', string="Payment order to reconcile",
type='many2one', relation='payment.order'),
}

View File

@@ -0,0 +1,221 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class payment_line(osv.osv):
'''
Add extra export_state and date_done fields; make destination bank account
mandatory, as it makes no sense to send payments into thin air.
Edit: Payments can be by cash too, which is prohibited by mandatory bank
accounts.
'''
_inherit = 'payment.line'
_columns = {
# New fields
'export_state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('rejected', 'Rejected'),
('done','Done'),
], 'State', select=True
),
'msg': fields.char('Message', size=255, required=False, readonly=True),
# Redefined fields: added states
'date_done': fields.datetime('Date Confirmed', select=True,
readonly=True),
'name': fields.char(
'Your Reference', size=64, required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication': fields.char(
'Communication', size=64, required=False,
help=("Used as the message between ordering customer and current "
"company. Depicts 'What do you want to say to the recipient"
" about this order ?'"
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication2': fields.char(
'Communication 2', size=128,
help='The successor message of Communication.',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'move_line_id': fields.many2one(
'account.move.line', 'Entry line',
domain=[('reconcile_id','=', False),
('account_id.type', '=','payable')
],
help=('This Entry Line will be referred for the information of '
'the ordering customer.'
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'amount_currency': fields.float(
'Amount in Partner Currency', digits=(16,2),
required=True,
help='Payment amount in the partner currency',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'currency': fields.many2one(
'res.currency', 'Partner Currency', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'bank_id': fields.many2one(
'res.partner.bank', 'Destination Bank account',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'order_id': fields.many2one(
'payment.order', 'Order', required=True,
ondelete='cascade', select=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'partner_id': fields.many2one(
'res.partner', string="Partner", required=True,
help='The Ordering Customer',
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date': fields.date(
'Payment Date',
help=("If no payment date is specified, the bank will treat this "
"payment line directly"
),
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
'state': fields.selection([
('normal','Free'),
('structured','Structured')
], 'Communication Type', required=True,
states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
),
}
_defaults = {
'export_state': lambda *a: 'draft',
'date_done': lambda *a: False,
'msg': lambda *a: '',
}
def fields_get(self, cr, uid, fields=None, context=None):
res = super(payment_line, self).fields_get(cr, uid, fields, context)
if 'communication' in res:
res['communication'].setdefault('states', {})
res['communication']['states']['structured'] = [('required', True)]
if 'communication2' in res:
res['communication2'].setdefault('states', {})
res['communication2']['states']['structured'] = [('readonly', True)]
res['communication2']['states']['normal'] = [('readonly', False)]
return res
"""
Hooks for processing direct debit orders, such as implemented in
account_direct_debit module.
"""
def get_storno_account_id(self, cr, uid, payment_line_id, amount,
currency_id, context=None):
"""
Hook for verifying a match of the payment line with the amount.
Return the account associated with the storno.
Used in account_banking interactive mode
:param payment_line_id: the single payment line id
:param amount: the (signed) amount debited from the bank account
:param currency: the bank account's currency *browse object*
:return: an account if there is a full match, False otherwise
:rtype: database id of an account.account resource.
"""
return False
def debit_storno(self, cr, uid, payment_line_id, amount,
currency_id, storno_retry=True, context=None):
"""
Hook for handling a canceled item of a direct debit order.
Presumably called from a bank statement import routine.
Decide on the direction that the invoice's workflow needs to take.
You may optionally return an incomplete reconcile for the caller
to reconcile the now void payment.
:param payment_line_id: the single payment line id
:param amount: the (negative) amount debited from the bank account
:param currency: the bank account's currency *browse object*
:param boolean storno_retry: whether the storno is considered fatal \
or not.
:return: an incomplete reconcile for the caller to fill
:rtype: database id of an account.move.reconcile resource.
"""
return False
payment_line()

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class payment_mode(orm.Model):
''' Restoring the payment type from version 5,
used to select the export wizard (if any) '''
_inherit = "payment.mode"
def suitable_bank_types(self, cr, uid, payment_mode_id=None, context=None):
""" Reinstates functional code for suitable bank type filtering.
Current code in account_payment is disfunctional.
"""
res = []
payment_mode = self.browse(
cr, uid, payment_mode_id, context)
if (payment_mode and payment_mode.type and
payment_mode.type.suitable_bank_types):
res = [type.code for type in payment_mode.type.suitable_bank_types]
return res
_columns = {
'type': fields.many2one(
'payment.mode.type', 'Payment type',
help='Select the Payment Type for the Payment Mode.'
),
}
payment_mode()

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class payment_mode_type(orm.Model):
_name= 'payment.mode.type'
_description= 'Payment Mode Type'
_columns= {
'name': fields.char(
'Name', size=64, required=True,
help='Payment Type'
),
'code': fields.char(
'Code', size=64, required=True,
help='Specify the Code for Payment Type'
),
# Setting suitable_bank_types to required pending
# https://bugs.launchpad.net/openobject-addons/+bug/786845
'suitable_bank_types': fields.many2many(
'res.partner.bank.type',
'bank_type_payment_type_rel',
'pay_type_id','bank_type_id',
'Suitable bank types', required=True),
'ir_model_id': fields.many2one(
'ir.model', 'Payment wizard',
help=('Select the Payment Wizard for payments of this type. '
'Leave empty for manual processing'),
domain=[('osv_memory', '=', True)],
),
'payment_order_type': fields.selection(
[('payment', 'Payment'),('debit', 'Direct debit')],
'Payment order type', required=True,
),
}
_defaults = {
'payment_order_type': lambda *a: 'payment',
}

View File

@@ -2,25 +2,29 @@
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
#
# All other contributions are (C) by their respective contributors
#
# All Rights Reserved
#
# 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 3 of the License, or
# (at your option) any later version.
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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.
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import datetime
from osv import osv
from openerp.osv import orm
from account_banking.struct import struct
from account_banking.parsers import convert
@@ -30,7 +34,7 @@ def str2date(str):
dt = convert.str2date(str, '%Y-%m-%d')
return datetime.date(dt.year, dt.month, dt.day)
class payment_order_create(osv.osv_memory):
class payment_order_create(orm.TransientModel):
_inherit = 'payment.order.create'
def create_payment(self, cr, uid, ids, context=None):
@@ -123,5 +127,3 @@ class payment_order_create(osv.osv_memory):
'currency': line.invoice and line.invoice.currency_id.id or line.journal_id.currency.id or line.journal_id.company_id.currency_id.id,
}, context=context)
return {'type': 'ir.actions.act_window_close'}
payment_order_create()

View File

@@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_payment_mode_type","payment.mode.type","model_payment_mode_type","account_payment.group_account_payment",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_payment_mode_type payment.mode.type model_payment_mode_type account_payment.group_account_payment 1 1 1 1

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Make buttons on payment order sensitive for extra states,
restore wizard functionality when making payments
-->
<record id="view_banking_payment_order_form_1" model="ir.ui.view">
<field name="name">account.payment.order.form.banking-1</field>
<field name="inherit_id" ref="account_payment.view_payment_order_form" />
<field name="model">payment.order</field>
<field name="type">form</field>
<field name="arch" type="xml">
<data>
<xpath expr="/form/group/button[@string='Select Invoices to Pay']"
position="replace">
<button colspan="2" name="%(account_payment.action_create_payment_order)s"
string="Select Invoices to Pay" type="action"
attrs="{'invisible':[('state','!=','draft')]}"
icon="gtk-find"
/>
</xpath>
<xpath expr="/form/group/button[@string='Make Payments']"
position="replace">
<button name="launch_wizard" states="open" string="Make Payments" type="object" icon="gtk-execute"/>
<newline/>
</xpath>
</data>
</field>
</record>
<record id="view_banking_payment_order_tree_1" model="ir.ui.view">
<field name="name">account.payment.order.tree.banking-1</field>
<field name="inherit_id" ref="account_payment.view_payment_order_tree" />
<field name="model">payment.order</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<button string="Make Payments" position="replace">
<button name="launch_wizard" states="open" string="Make Payments" type="object" icon="gtk-execute"/>
</button>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="transaction_wizard">
<field name="name">transaction.wizard</field>
<field name="model">banking.transaction.wizard</field>
<field name="inherit_id"
ref="account_banking.transaction_wizard_first" />
<field name="arch" type="xml">
<field name="invoice_ids" position="before">
<field name="payment_order_ids" invisible="True"/>
</field>
<xpath expr="//group/separator[@string='Multiple matches']/.."
position="after">
<field name='payment_line_id'
attrs="{'invisible': [
('match_type', '!=', 'storno'),
('match_type', '!=', 'payment')]
}" />
</xpath>
<field name="move_line_id" position="after">
<field name='payment_order_id'
attrs="{'readonly': [
('match_multi', '=', False)],
'invisible': [
('match_type', '!=', 'payment_order')]}"
domain="[('id', 'in', payment_order_ids[0][2])]"
/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Add the payment mode type to the payment mode views -->
<record id="view_payment_mode_form_inherit" model="ir.ui.view">
<field name="name">payment.mode.form.inherit</field>
<field name="model">payment.mode</field>
<field name="inherit_id" ref="account_payment.view_payment_mode_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="type"/>
</field>
</field>
</record>
<record id="view_payment_mode_tree_inherit" model="ir.ui.view">
<field name="name">payment.mode.tree.inherit</field>
<field name="model">payment.mode</field>
<field name="inherit_id" ref="account_payment.view_payment_mode_tree"/>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="type"/>
</field>
</field>
</record>
<!-- basic view for payment mode type -->
<record model="ir.ui.view" id="view_payment_mode_type_form">
<field name="name">view.payment.mode.type.form</field>
<field name="model">payment.mode.type</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form>
<field name="name" />
<field name="code" />
<field name="suitable_bank_types"/>
<field name="payment_order_type"/>
<field name="ir_model_id"/>
</form>
</field>
</record>
</data>
</openerp>

View File

@@ -1,20 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) EduSense BV <http://www.edusense.nl>
All rights reserved.
The licence is in the file __terp__.py
-->
<openerp>
<data>
<!-- New activity for workflow payment order: sent -->
<record id="act_sent" model="workflow.activity">
<record id="account_banking.act_sent" model="workflow.activity">
<field name="name">sent</field>
<field name="wkf_id" ref="account_payment.wkf_payment_order"/>
<field name="action">action_sent()</field>
<field name="kind">function</field>
</record>
<!-- New activity for workflow payment order: rejected -->
<record id="act_rejected" model="workflow.activity">
<record id="account_banking.act_rejected" model="workflow.activity">
<field name="name">rejected</field>
<field name="wkf_id" ref="account_payment.wkf_payment_order"/>
<field name="action">action_rejected()
@@ -22,21 +17,21 @@ write({'state':'rejected'})</field>
<field name="kind">function</field>
</record>
<!-- Add new transition sent -> done -->
<record id="trans_sent_done" model="workflow.transition">
<field name="act_from" ref="act_sent"/>
<record id="account_banking.trans_sent_done" model="workflow.transition">
<field name="act_from" ref="account_banking.act_sent"/>
<field name="act_to" ref="account_payment.act_done"/>
<field name="signal">done</field>
</record>
<!-- Add new transition sent -> rejected -->
<record id="trans_sent_rejected" model="workflow.transition">
<field name="act_from" ref="act_sent"/>
<field name="act_to" ref="act_rejected"/>
<record id="account_banking.trans_sent_rejected" model="workflow.transition">
<field name="act_from" ref="account_banking.act_sent"/>
<field name="act_to" ref="account_banking.act_rejected"/>
<field name="signal">rejected</field>
</record>
<!-- Rewrite existing open -> done transition to include 'sent' -->
<record id="account_payment.trans_open_done" model="workflow.transition">
<field name="act_from" ref="account_payment.act_open"/>
<field name="act_to" ref="act_sent"/>
<field name="act_to" ref="account_banking.act_sent"/>
<field name="signal">sent</field>
</record>
</data>