Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions contract_recurring_payment/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
==================
Generic contract recurring payment
==================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:6694fe97ba2c4c5343006af38811c58f4b864357bf894956bd3e4b51eae88b72
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge2| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2|

This addon make contract invoicing cron plan each contract in a job instead of creating all invoices in one transaction

**Table of contents**

.. contents::
:local:

Usage
=====

The feature to create generic recurring payment

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/contract/issues>`_.
In case of trouble, please check there if your issue has already been reported.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Binhex


Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.


This module is part of the `OCA/contract <https://github.com/OCA/contract/tree/16.0/contract>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions contract_recurring_payment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions contract_recurring_payment/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Generic contract recurring payment",
"summary": """
Generic Contract recurring payment".""",
"author": "Binhex,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/contract",
"category": "Contract",
"version": "16.0.1.0.1",
"depends": [
"account",
"contract",
"contract_payment_mode",
"payment",
],
"license": "AGPL-3",
"data": [
"data/ir_cron_recurring_payment.xml",
"views/contract_contract_view.xml",
],
"images": ["static/src/description/icon.png"],
}
15 changes: 15 additions & 0 deletions contract_recurring_payment/data/ir_cron_recurring_payment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="generate_recurring_payment_cron" forcecreate='True' model="ir.cron">
<field name="name">Generate recurring payment</field>
<field eval="True" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="model_id" ref="contract.model_contract_contract" />
<field name="state">code</field>
<field name="code">model.cron_recurring_payment()</field>
</record>

</odoo>
3 changes: 3 additions & 0 deletions contract_recurring_payment/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import contract_contract
from . import account_payment
from . import account_payment_mode
7 changes: 7 additions & 0 deletions contract_recurring_payment/models/account_payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import fields, models


class AccountPayment(models.Model):
_inherit = "account.payment"

contract_id = fields.Many2one("contract.contract", string="Contract")
13 changes: 13 additions & 0 deletions contract_recurring_payment/models/account_payment_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import models


class AccountPaymentMode(models.Model):
_inherit = "account.payment.mode"

def _create_recurring_payments(self, invoices, contract_id):
"""
Generic method for each payment mode
:param invoices:
:param contract_id:
:return:
"""
49 changes: 49 additions & 0 deletions contract_recurring_payment/models/contract_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from datetime import datetime

from odoo import api, fields, models

SELECTION_PAYMENT_TYPE = [
("fixed_date", "Fixed Date"),
("invoice_due_date", "Invoice Due Date"),
]


class ContractContract(models.Model):
_inherit = "contract.contract"

allow_use_payment_token = fields.Boolean(
default=False,
string="Allow use payment token",
help="Allow use payment token on recurring payment",
)

payment_mode_code = fields.Char(related="payment_mode_id.payment_method_id.code")
payment_ids = fields.One2many(
"account.payment",
"contract_id",
string="Payments",
)
next_recurring_payment_date = fields.Date(string="Next Payment Date")

@api.model
def cron_recurring_payment(self):
"""
Check contracts and generate recurring payments
:return:
"""
contracts = self.search(
[("active", "=", True), ("allow_use_payment_token", "=", True)]
)

for contract in contracts:
if contract.next_recurring_payment_date == datetime.utcnow().date():
if contract.payment_mode_id:
invoices = contract._get_related_invoices().filtered(
lambda account_move: account_move.state == "posted"
and account_move.amount_residual_signed > 0
and account_move.payment_state in ["not_paid", "partial"]
)
if invoices:
contract.payment_mode_id._create_recurring_payments(
invoices=invoices, contract_id=contract
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions contract_recurring_payment/views/contract_contract_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<record
id="contract_contract_form_view_inherit_recurring_payment"
model="ir.ui.view"
>
<field name="name">contract.contract form view (in contract).form</field>
<field name="model">contract.contract</field>
<field name="inherit_id" ref="contract.contract_contract_form_view" />
<field name="arch" type="xml">
<xpath expr="//group[@name='recurring_type']" position="after">
<group name="recurring_payment" string="Recurring payment">
<group>
<field name="allow_use_payment_token" />
</group>
<group>
<field name="next_recurring_payment_date" />
</group>
</group>
</xpath>
<notebook position="inside">
<page name="recurring_payments" string="Recurring Payments">
<field name="payment_ids">
<tree>
<field name="date" />
<field name="name" />
<field name="amount_signed" />
<field name="state" />
</tree>
</field>
</page>
</notebook>
</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions contract_recurring_payment_ach/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
18 changes: 18 additions & 0 deletions contract_recurring_payment_ach/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "ACH recurring payment from contract",
"summary": """
Stripe recurring payment from contract""",
"author": "Binhex, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/contract",
"category": "Contract",
"version": "16.0.1.0.1",
"depends": [
"contract_recurring_payment",
"account_banking_mandate",
"account_banking_ach_direct_debit",
],
"data": [
"views/contract_view.xml",
],
"images": ["static/src/description/icon.png"],
}
2 changes: 2 additions & 0 deletions contract_recurring_payment_ach/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import account_payment_mode
from . import contract_contract
101 changes: 101 additions & 0 deletions contract_recurring_payment_ach/models/account_payment_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from datetime import datetime

from odoo import models


class AccountPaymentMode(models.Model):
_inherit = "account.payment.mode"

def create_payment_order_line(
self, contract_id, invoice_move_line, account_payment_order_id
):
if invoice_move_line.currency_id:
currency_id = invoice_move_line.currency_id.id
amount_currency = invoice_move_line.amount_residual_currency
else:
currency_id = invoice_move_line.company_id.currency_id.id
amount_currency = invoice_move_line.amount_residual
values = {
"partner_id": contract_id.partner_id.id,
"move_line_id": invoice_move_line.id,
"date": datetime.now().date(),
"currency_id": currency_id,
"amount_currency": amount_currency,
"communication_type": "normal",
"communication": invoice_move_line.move_id.name,
"mandate_id": contract_id.mandate_id.id,
"order_id": account_payment_order_id.id,
"partner_bank_id": contract_id.res_partner_bank_id.id,
}
self.env["account.payment.line"].sudo().create(values)

def _create_recurring_payments(self, invoices, contract_id):
"""
Generic method for each payment mode
:param invoices:
:return:
"""
self.ensure_one()
if self.payment_method_id.code == "ACH-In":
active_payment_order = self.env["account.payment.order"].search(
[
(
"journal_id",
"=",
contract_id.payment_mode_id.fixed_journal_id.id,
),
("state", "=", "draft"),
("payment_type", "=", "inbound"),
(
"payment_mode_id.payment_method_id.code",
"=",
self.payment_method_id.code,
),
],
limit=1,
)

if active_payment_order:
for invoice in invoices:
invoice_move_line = self.get_reconciled_line_from_moves(invoice)
self.create_payment_order_line(
contract_id=contract_id,
invoice_move_line=invoice_move_line,
account_payment_order_id=active_payment_order,
)
else:
company_partner_bank_id = (
self.env["res.partner.bank"]
.sudo()
.search(
[("partner_id", "=", contract_id.company_id.partner_id.id)],
limit=1,
)
)
account_payment_order = (
self.env["account.payment.order"]
.sudo()
.create(
{
"payment_mode_id": self.id,
"journal_id": self.fixed_journal_id.id,
"description": "Payment order generated automatically from contract",
"company_id": contract_id.company_id.id,
"company_partner_bank_id": company_partner_bank_id.id,
}
)
)

for invoice in invoices:
invoice_move_line = self.get_reconciled_line_from_moves(invoice)
self.create_payment_order_line(
contract_id, invoice_move_line, account_payment_order
)

else:
return super()._create_recurring_payments(invoices, contract_id)

def get_reconciled_line_from_moves(self, invoice):
return invoice.mapped("line_ids").filtered(
lambda line: not line.reconciled and line.account_id.reconcile == True
)
8 changes: 8 additions & 0 deletions contract_recurring_payment_ach/models/contract_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from odoo import fields, models


class ContractContract(models.Model):
_inherit = "contract.contract"

res_partner_bank_id = fields.Many2one("res.partner.bank", string="Bank Account")
mandate_id = fields.Many2one("account.banking.mandate", string="Mandate")
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions contract_recurring_payment_ach/views/contract_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--FORM view-->
<record id="contract_contract_form_view" model="ir.ui.view">
<field
name="name"
>contract.contract form view (in contract_recurring_payment_ach)
</field>
<field name="model">contract.contract</field>
<field name="inherit_id" ref="contract.contract_contract_form_view" />
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="payment_mode_code" invisible="1" />
<field
name="res_partner_bank_id"
domain="[('partner_id', '=', partner_id)]"
attrs="{'invisible': [('payment_mode_code', '!=', 'ACH-In')],'required': [('payment_mode_code', '=', 'ACH-In')]}"
/>
<field
name="mandate_id"
domain="[('partner_bank_id', '=', res_partner_bank_id),('state', '=', 'valid')]"
attrs="{'invisible': [('payment_mode_code', '!=', 'ACH-In')],'required': [('payment_mode_code', '=', 'ACH-In')]}"
/>
</field>
</field>
</record>
</odoo>
Loading