Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found
Sélectionner une révision Git
Loading items

Cible

Sélectionner le projet cible
  • lefilament/cgscop/cgscop_cotisation
  • hsilvant/cgscop_cotisation
2 résultats
Sélectionner une révision Git
Loading items
Afficher les modifications
Validations sur la source (59)
Affichage de avec 1100 ajouts et 413 suppressions
# Configuration for known file extensions
[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{json,yml,yaml,rst,md}]
indent_size = 2
# Do not configure editor for libs and autogenerated content
[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
charset = unset
end_of_line = unset
indent_size = unset
indent_style = unset
insert_final_newline = false
trim_trailing_whitespace = false
env:
browser: true
es6: true
# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
parserOptions:
ecmaVersion: 2017
overrides:
- files:
- "**/*.esm.js"
parserOptions:
sourceType: module
# Globals available in Odoo that shouldn't produce errorings
globals:
_: readonly
$: readonly
fuzzy: readonly
jQuery: readonly
moment: readonly
odoo: readonly
openerp: readonly
owl: readonly
# Styling is handled by Prettier, so we only need to enable AST rules;
# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
rules:
accessor-pairs: warn
array-callback-return: warn
callback-return: warn
capitalized-comments:
- warn
- always
- ignoreConsecutiveComments: true
ignoreInlineComments: true
complexity:
- warn
- 15
constructor-super: warn
dot-notation: warn
eqeqeq: warn
global-require: warn
handle-callback-err: warn
id-blacklist: warn
id-match: warn
init-declarations: error
max-depth: warn
max-nested-callbacks: warn
max-statements-per-line: warn
no-alert: warn
no-array-constructor: warn
no-caller: warn
no-case-declarations: warn
no-class-assign: warn
no-cond-assign: error
no-const-assign: error
no-constant-condition: warn
no-control-regex: warn
no-debugger: error
no-delete-var: warn
no-div-regex: warn
no-dupe-args: error
no-dupe-class-members: error
no-dupe-keys: error
no-duplicate-case: error
no-duplicate-imports: error
no-else-return: warn
no-empty-character-class: warn
no-empty-function: error
no-empty-pattern: error
no-empty: warn
no-eq-null: error
no-eval: error
no-ex-assign: error
no-extend-native: warn
no-extra-bind: warn
no-extra-boolean-cast: warn
no-extra-label: warn
no-fallthrough: warn
no-func-assign: error
no-global-assign: error
no-implicit-coercion:
- warn
- allow: ["~"]
no-implicit-globals: warn
no-implied-eval: warn
no-inline-comments: warn
no-inner-declarations: warn
no-invalid-regexp: warn
no-irregular-whitespace: warn
no-iterator: warn
no-label-var: warn
no-labels: warn
no-lone-blocks: warn
no-lonely-if: error
no-mixed-requires: error
no-multi-str: warn
no-native-reassign: error
no-negated-condition: warn
no-negated-in-lhs: error
no-new-func: warn
no-new-object: warn
no-new-require: warn
no-new-symbol: warn
no-new-wrappers: warn
no-new: warn
no-obj-calls: warn
no-octal-escape: warn
no-octal: warn
no-param-reassign: warn
no-path-concat: warn
no-process-env: warn
no-process-exit: warn
no-proto: warn
no-prototype-builtins: warn
no-redeclare: warn
no-regex-spaces: warn
no-restricted-globals: warn
no-restricted-imports: warn
no-restricted-modules: warn
no-restricted-syntax: warn
no-return-assign: error
no-script-url: warn
no-self-assign: warn
no-self-compare: warn
no-sequences: warn
no-shadow-restricted-names: warn
no-shadow: warn
no-sparse-arrays: warn
no-sync: warn
no-this-before-super: warn
no-throw-literal: warn
no-undef-init: warn
no-undef: error
no-unmodified-loop-condition: warn
no-unneeded-ternary: error
no-unreachable: error
no-unsafe-finally: error
no-unused-expressions: error
no-unused-labels: error
no-unused-vars: error
no-use-before-define: error
no-useless-call: warn
no-useless-computed-key: warn
no-useless-concat: warn
no-useless-constructor: warn
no-useless-escape: warn
no-useless-rename: warn
no-void: warn
no-with: warn
operator-assignment: [error, always]
prefer-const: warn
radix: warn
require-yield: warn
sort-imports: warn
spaced-comment: [error, always]
strict: [error, function]
use-isnan: error
valid-jsdoc:
- warn
- prefer:
arg: param
argument: param
augments: extends
constructor: class
exception: throws
func: function
method: function
prop: property
return: returns
virtual: abstract
yield: yields
preferType:
array: Array
bool: Boolean
boolean: Boolean
number: Number
object: Object
str: String
string: String
requireParamDescription: false
requireReturn: false
requireReturnDescription: false
requireReturnType: false
valid-typeof: warn
yoda: warn
[flake8]
max-line-length = 88
max-complexity = 16
# B = bugbear
# B9 = bugbear opinionated (incl line length)
select = C,E,F,W,B,B9
# E203: whitespace before ':' (black behaviour)
# E501: flake8 line length (covered by bugbear B950)
# W503: line break before binary operator (black behaviour)
ignore = E203,E501,W503
per-file-ignores=
__init__.py:F401
.*
*.pyc
!.gitignore
\ No newline at end of file
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
/.venv
/.pytest_cache
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
*.eggs
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Pycharm
.idea
# Eclipse
.settings
# Visual Studio cache/options directory
.vs/
.vscode
# OSX Files
.DS_Store
# Django stuff:
*.log
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Sphinx documentation
docs/_build/
# Backup files
*~
*.swp
# OCA rules
!static/lib/
[settings]
; see https://github.com/psf/black
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
combine_as_imports=True
use_parentheses=True
line_length=88
known_odoo=odoo
known_odoo_addons=odoo.addons
sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
default_section=THIRDPARTY
ensure_newline_before_comments = True
exclude: |
(?x)
# NOT INSTALLABLE ADDONS
# END NOT INSTALLABLE ADDONS
# Files and folders generated by bots, to avoid loops
^setup/|/static/description/index\.html$|
# We don't want to mess with tool-generated files
.svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
# Maybe reactivate this when all README files include prettier ignore tags?
^README\.md$|
# Library files can have extraneous formatting (even minimized)
/static/(src/)?lib/|
# Repos using Sphinx to generate docs don't need prettying
^docs/_templates/.*\.html$|
# You don't usually want a bot to modify your legal texts
(LICENSE.*|COPYING.*)
default_language_version:
python: python3
node: "14.13.0"
repos:
- repo: local
hooks:
# These files are most likely copier diff rejection junks; if found,
# review them manually, fix the problem (if needed) and remove them
- id: forbidden-files
name: forbidden files
entry: found forbidden files; remove them
language: fail
files: "\\.rej$"
- id: en-po-files
name: en.po files cannot exist
entry: found a en.po file
language: fail
files: '[a-zA-Z0-9_]*/i18n/en\.po$'
- repo: https://github.com/oca/maintainer-tools
rev: ab1d7f6
hooks:
# update the NOT INSTALLABLE ADDONS section above
- id: oca-update-pre-commit-excluded-addons
- id: oca-fix-manifest-website
args: ["https://le-filament.com"]
- repo: https://github.com/myint/autoflake
rev: v1.4
hooks:
- id: autoflake
args:
- --expand-star-imports
- --ignore-init-module-imports
- --in-place
- --remove-all-unused-imports
- --remove-duplicate-keys
- --remove-unused-variables
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.1.2
hooks:
- id: prettier
name: prettier (with plugin-xml)
exclude: ^datas/
additional_dependencies:
- "prettier@2.1.2"
- "@prettier/plugin-xml@0.12.0"
args:
- --plugin=@prettier/plugin-xml
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v7.8.1
hooks:
- id: eslint
verbose: true
args:
- --color
- --fix
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
# exclude autogenerated files
exclude: /README\.rst$|\.pot?$
- id: end-of-file-fixer
# exclude autogenerated files
exclude: /README\.rst$|\.pot?$
- id: debug-statements
- id: fix-encoding-pragma
args: ["--remove"]
- id: check-case-conflict
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-merge-conflict
# exclude files where underlines are not distinguishable from merge conflicts
exclude: /README\.rst$|^docs/.*\.rst$
- id: check-symlinks
- id: check-xml
- id: mixed-line-ending
args: ["--fix=lf"]
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
hooks:
- id: pyupgrade
args: ["--keep-percent-format"]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
name: isort except __init__.py
args:
- --settings=.
exclude: /__init__\.py$
- repo: https://github.com/PyCQA/flake8
rev: 3.8.3
hooks:
- id: flake8
name: flake8
additional_dependencies: ["flake8-bugbear==20.1.4"]
- repo: https://github.com/OCA/pylint-odoo
rev: 7.0.2
hooks:
- id: pylint_odoo
name: pylint with optional checks
args:
- --rcfile=.pylintrc
- --exit-zero
verbose: true
- id: pylint_odoo
args:
- --rcfile=.pylintrc-mandatory
# Defaults for all prettier-supported languages.
# Prettier will complete this with settings from .editorconfig file.
bracketSpacing: false
printWidth: 88
proseWrap: always
semi: true
trailingComma: "es5"
xmlWhitespaceSensitivity: "strict"
[MASTER]
load-plugins=pylint_odoo
score=n
[ODOOLINT]
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
manifest_required_authors=Le Filament
manifest_required_keys=license
manifest_deprecated_keys=description,active
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
valid_odoo_versions=14.0
[MESSAGES CONTROL]
disable=all
# This .pylintrc contains optional AND mandatory checks and is meant to be
# loaded in an IDE to have it check everything, in the hope this will make
# optional checks more visible to contributors who otherwise never look at a
# green travis to see optional checks that failed.
# .pylintrc-mandatory containing only mandatory checks is used the pre-commit
# config as a blocking check.
enable=anomalous-backslash-in-string,
api-one-deprecated,
api-one-multi-together,
assignment-from-none,
attribute-deprecated,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
duplicate-xml-record-id,
eval-referenced,
eval-used,
incoherent-interpreter-exec-perm,
license-allowed,
manifest-author-string,
manifest-deprecated-key,
manifest-required-author,
manifest-required-key,
manifest-version-format,
method-compute,
method-inverse,
method-required-super,
method-search,
openerp-exception-warning,
pointless-statement,
pointless-string-statement,
print-used,
redundant-keyword-arg,
redundant-modulename-xml,
reimported,
relative-import,
return-in-init,
rst-syntax-error,
sql-injection,
too-few-format-args,
translation-field,
translation-required,
unreachable,
use-vim-comment,
wrong-tabs-instead-of-spaces,
xml-syntax-error,
# messages that do not cause the lint step to fail
consider-merging-classes-inherited,
create-user-wo-reset-password,
dangerous-filter-wo-user,
deprecated-module,
file-not-used,
invalid-commit,
missing-manifest-dependency,
missing-newline-extrafiles,
no-utf8-coding-comment,
odoo-addons-relative-import,
old-api7-method-defined,
redefined-builtin,
too-complex,
unnecessary-utf8-coding-comment
[REPORTS]
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
output-format=colorized
reports=no
[MASTER]
load-plugins=pylint_odoo
score=n
[ODOOLINT]
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
manifest_required_authors=Le Filament
manifest_required_keys=license
manifest_deprecated_keys=description,active
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
valid_odoo_versions=14.0
[MESSAGES CONTROL]
disable=all
enable=anomalous-backslash-in-string,
api-one-deprecated,
api-one-multi-together,
assignment-from-none,
attribute-deprecated,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
duplicate-xml-record-id,
eval-referenced,
eval-used,
incoherent-interpreter-exec-perm,
license-allowed,
manifest-author-string,
manifest-deprecated-key,
manifest-required-author,
manifest-required-key,
manifest-version-format,
method-compute,
method-inverse,
method-required-super,
method-search,
openerp-exception-warning,
pointless-statement,
pointless-string-statement,
print-used,
redundant-keyword-arg,
redundant-modulename-xml,
reimported,
relative-import,
return-in-init,
rst-syntax-error,
sql-injection,
too-few-format-args,
translation-field,
translation-required,
unreachable,
use-vim-comment,
wrong-tabs-instead-of-spaces,
xml-syntax-error
[REPORTS]
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
output-format=colorized
reports=no
Le mode du fichier est passé de 100755 à 100644
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# © 2022 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import wizard
from odoo import SUPERUSER_ID, api
from . import models, report, wizard
from odoo import api, SUPERUSER_ID
def _configure_journals(cr, registry):
"""Setting journal and property field (if needed)"""
......@@ -12,19 +12,29 @@ def _configure_journals(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
# if we already have a coa installed, create journal and set property field
company_ids = env['res.company'].search([('chart_template_id', '!=', False)])
company_ids = env["res.company"].search([("chart_template_id", "!=", False)])
for company_id in company_ids:
journal_id = env['account.journal'].search([
('name', '=', 'Cotisations'),
('company_id', '=', company_id.id),
('type', '=', 'sale')], limit=1).id
journal_id = (
env["account.journal"]
.search(
[
("name", "=", "Cotisations"),
("company_id", "=", company_id.id),
("type", "=", "sale"),
],
limit=1,
)
.id
)
if not journal_id:
env['account.journal'].create({
'name': 'Cotisations',
'type': 'sale',
'code': 'COT',
'company_id': company_id.id,
'show_on_dashboard': False,
'sequence': 6,
})
env["account.journal"].create(
{
"name": "Cotisations",
"type": "sale",
"code": "COT",
"company_id": company_id.id,
"show_on_dashboard": False,
"sequence": 6,
}
)
# © 2022 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "CG SCOP - Cotisations",
"summary": "CG SCOP - Cotisations",
"version": "12.0.1.1.0",
"development_status": "Production/Stable",
"version": "14.0.1.0.0",
"author": "Le Filament",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"account",
"account_banking_mandate",
"account_banking_sepa_direct_debit",
"cgscop_liste_ministere",
"account_payment_order",
"cgscop_account",
"cgscop_liasse_fiscale",
"cgscop_partner",
"multi_company_menu",
"lefilament_sales",
],
"data": [
"security/security_rules.xml",
"security/ir.model.access.csv",
"views/account_invoice.xml",
"datas/mail_data.xml",
"views/account_banking_mandate.xml",
"views/account_move.xml",
"views/account_payment_term.xml",
"views/account_payment_order.xml",
"views/account_payment_line.xml",
"views/res_config_settings.xml",
"views/res_partner.xml",
"views/scop_cotisation_task.xml",
"report/scop_contribution_report.xml",
"wizard/account_payment_line_create_view.xml",
"wizard/account_banking_mandate_change_wizard.xml",
],
"qweb": [
"static/src/xml/*.xml",
],
'post_init_hook': '_configure_journals',
"post_init_hook": "_configure_journals",
}
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<!-- Email d'avis de prélèvement -->
<record id="email_template_payment_order" model="mail.template">
<field name="name">CG SCOP : Prélèvements</field>
<field name="model_id" ref="account_payment_order.model_account_payment_order"/>
<field name="email_from">"Confédération Générale des Scop et Scic" &lt;administratif.cg@scop.coop&gt;</field>
<field name="reply_to">"Confédération Générale des Scop et Scic" &lt;administratif.cg@scop.coop&gt;</field>
<field name="partner_to">${object.get_recipients()}</field>
<field name="subject">CG SCOP : Avis de prélèvement</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
<p>${object.get_email_values().get("partner_id").member_number} - ${object.get_email_values().get("partner_id").name}</p>
<p>&#160;</p>
<p>Chère Coopératrice, Cher Coopérateur,</p>
<p>&#160;</p>
<p>&#160;</p>
<p>
Vous avez choisi de régler vos cotisations à la Confédération générale des Scop et des Scic par prélèvement bancaire.
</p>
<p>
Nous vous informons que nous avons procédé ce jour à un ordre de prélèvement qui sera présenté au débit de votre compte le ${object.get_email_values().get("date").strftime('%d/%m/%Y')}.
</p>
<p>&#160;</p>
<p>
Le mandat de prélèvement enregistré pour votre coopérative est le suivant : ${object.get_email_values().get("bank")}
</p>
<p>&#160;</p>
<p>
Ce courriel vous est envoyé à titre informatif, l’ordre de prélèvement ne peut plus être arrêté en banque, sauf par vos soins en prenant contact avec votre établissement bancaire.
</p>
<p>Dans ce cas, nous vous remercions de bien vouloir nous en informer.</p>
<p>&#160;</p>
<p>
En cas de difficulté économique, nous vous invitons également à vous rapprocher de votre délégation régionale.
</p>
<p>&#160;</p>
<p>
Ce prélèvement correspond au paiement des cotisations :
<ul>
% for line in object.get_email_values().get("deadline"):
% set quarter = "er" if line.get("quarter") == "1" else "ème"
<li>
${line.get("quarter")}${quarter} Trimestre ${line.get("year")} : ${format_amount(line.get("amount"), object.company_currency_id)}
</li>
% endfor
</ul>
</p>
<p>&#160;</p>
<p>
Nous vous rappelons que les bordereaux de cotisations sont mis à disposition dans <a href="https://extranet.scop.coop/">votre espace extranet</a> et ne sont plus transmis par courrier.
</p>
<p>
Pour toute question relative à vos cotisations appelées par la CGSCOP, notre équipe est à votre disposition par téléphone au 01 44 85 47 00 ou par courriel à <a href="mailto:administratif.cg@scop.coop">administratif.cg@scop.coop</a>.
</p>
<p>&#160;</p>
<p
>Nous vous adressons nos salutations sincères et coopératives,</p>
<p>&#160;</p>
<p>L'équipe de la CGScop</p>
</p>
</div>
</field>
<field name="lang">fr_FR</field>
<field name="auto_delete" eval="False" />
</record>
</data>
</odoo>
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import account_invoice
from . import account_banking_mandate
from . import account_move
from . import account_payment_order
from . import account_payment_term
from . import chart_template
from . import res_company
from . import res_config_settings
from . import scop_contribution
from . import res_partner
from . import scop_cotisation
from . import scop_cotisation_task
# © 2020 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
from odoo.exceptions import UserError
class AccountBankingMandate(models.Model):
_inherit = "account.banking.mandate"
# ------------------------------------------------------
# Button functions
# ------------------------------------------------------
def set_payment_order(self):
self.ensure_one()
payment_mode = self.env["account.payment.mode"].search(
[("payment_method_id.code", "=", "sepa_direct_debit")]
)
if not payment_mode:
raise UserError(_("Aucun mode de prélèvement SEPA configuré."))
self.partner_id.customer_payment_mode_id = payment_mode
def set_invoice_mandate(self):
return {
"name": "Affectation du nouveau mandat",
"type": "ir.actions.act_window",
"res_model": "account.banking.mandate.change.wizard",
"views": [[False, "form"]],
"target": "new",
}
# ------------------------------------------------------
# Business functions
# ------------------------------------------------------
def _select_open_invoices(self):
self.ensure_one()
invoice_ids = self.env["account.move"].search(
[
("partner_id", "=", self.partner_id.id),
("move_type", "in", ("out_invoice", "out_refund")),
("state", "in", ("draft", "posted")),
("payment_state", "in", ("draft", "not_paid", "partial")),
]
)
return invoice_ids
def _change_invoice_mandate(self, invoice_ids):
"""
Affecte le mandat courant pur la facture passée en paramètre
:param invoice : objet account.move
"""
payment_mode = self.env["account.payment.mode"].search(
[
("payment_method_id.code", "=", "sepa_direct_debit"),
("company_id", "=", self.env.company.id),
]
)
if not payment_mode:
raise UserError(_("Aucun mode de prélèvement SEPA configuré."))
for invoice in invoice_ids:
invoice.update(
{
"payment_mode_id": payment_mode.id,
"partner_bank_id": self.partner_bank_id.id,
"mandate_id": self.id,
}
)
def _remove_invoice_mandate(self, invoice_ids):
"""
Supprime le mandat courant pur la facture passée en paramètre
:param invoice : objet account.move
"""
payment_mode = self.env["account.payment.mode"].search(
[("name", "ilike", "Virement"), ("company_id", "=", self.env.company.id)],
limit=1,
)
for invoice in invoice_ids:
invoice.update(
{
"payment_mode_id": payment_mode.id if payment_mode else None,
"partner_bank_id": False,
"mandate_id": False,
}
)
self.partner_id.customer_payment_mode_id = payment_mode
# © 2020 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime
import json
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class ScopAccountInvoice(models.Model):
_inherit = "account.invoice"
@api.model
def default_get(self, fields):
"""
Attribue la valeur du journal des cotisations par défaut si la facture
est de type 'is_contribution'
Affecte le type 'is_contribution' par défaut si la facture est sur le
journal des cotisations
:param fields:
:return:
"""
res = super(ScopAccountInvoice, self).default_get(fields)
if res.get('is_contribution'):
res['journal_id'] =\
self.env.user.company_id.contribution_journal_id.id
else:
if res.get('journal_id') == \
self.env.user.company_id.contribution_journal_id.id:
res['is_contribution'] = True
return res
liasse_fiscale_id = fields.Many2one(
comodel_name='scop.liasse.fiscale',
string='Liasse Fiscale')
year = fields.Integer('Année de cotisation')
is_contribution = fields.Boolean("Cotisation", default=False)
type_contribution_id = fields.Many2one(
comodel_name="scop.contribution.type",
string="Type de cotisation",
readonly=True)
partner_ur_id = fields.Many2one(
comodel_name='union.regionale',
string='UR Adhérent',
related='partner_id.ur_id',
store=True
)
contribution_id = fields.Many2one(
comodel_name='scop.contribution',
string='Ligne de cotisation')
nb_quarter = fields.Selection(
string='Nombre de trimestres de cotisation',
selection=[(1, '1'),
(2, '2'),
(3, '3'),
(4, '4')],
default=4,
required=True)
is_sdd = fields.Boolean(
'Au prélèvement',
compute='compute_is_sdd',
search='_search_is_sdd')
# ------------------------------------------------------
# Computed field
# ------------------------------------------------------
@api.multi
def compute_is_sdd(self):
sdd_id = self.env.ref(
'account_banking_sepa_direct_debit.sepa_direct_debit').id
for invoice in self:
if invoice.payment_mode_id and invoice.payment_mode_id.payment_method_id.id == sdd_id:
invoice.is_sdd = True
else:
invoice.is_sdd = False
@api.multi
def _search_is_sdd(self, operator, value):
recs = self.search([]).filtered(lambda x: x.is_sdd is True)
if recs:
return [('id', 'in', [x.id for x in recs])]
# ------------------------------------------------------
# Override Parent
# ------------------------------------------------------
@api.multi
def action_invoice_open(self):
"""
Création d'une ligne dans scop.contribution
quand une facture cotisation devient valide
"""
results = super(ScopAccountInvoice, self).action_invoice_open()
for inv in self:
if inv.is_contribution:
inv.set_scop_contribution()
return results
@api.multi
def action_move_create(self):
"""
Complete override parent
Pass invoice in payment_term.compute function to generate payment
schedule
:return: True
"""
account_move = self.env['account.move']
for inv in self:
if not inv.journal_id.sequence_id:
raise UserError(_('Please define sequence on the journal related to this invoice.'))
if not inv.invoice_line_ids.filtered(lambda line: line.account_id):
raise UserError(_('Please add at least one invoice line.'))
if inv.move_id:
continue
if not inv.date_invoice:
inv.write({'date_invoice': fields.Date.context_today(self)})
if not inv.date_due:
inv.write({'date_due': inv.date_invoice})
company_currency = inv.company_id.currency_id
# create move lines
# (one per invoice line + eventual taxes and analytic lines)
iml = inv.invoice_line_move_line_get()
iml += inv.tax_line_move_line_get()
diff_currency = inv.currency_id != company_currency
# create one move line for the total and possibly adjust
# the other lines amount
total, total_currency, iml = inv.compute_invoice_totals(
company_currency, iml)
name = inv.name or ''
if inv.payment_term_id:
totlines = inv.payment_term_id.with_context(
currency_id=company_currency.id).compute(total, inv.date_invoice, inv)[0]
res_amount_currency = total_currency
for i, t in enumerate(totlines):
if inv.currency_id != company_currency:
amount_currency = company_currency._convert(
t[1],
inv.currency_id,
inv.company_id,
inv._get_currency_rate_date() or fields.Date.today())
else:
amount_currency = False
# last line: add the diff
res_amount_currency -= amount_currency or 0
if i + 1 == len(totlines):
amount_currency += res_amount_currency
iml.append({
'type': 'dest',
'name': name,
'price': t[1],
'account_id': inv.account_id.id,
'date_maturity': t[0],
'amount_currency': diff_currency and amount_currency,
'currency_id': diff_currency and inv.currency_id.id,
'invoice_id': inv.id
})
else:
iml.append({
'type': 'dest',
'name': name,
'price': total,
'account_id': inv.account_id.id,
'date_maturity': inv.date_due,
'amount_currency': diff_currency and total_currency,
'currency_id': diff_currency and inv.currency_id.id,
'invoice_id': inv.id
})
part = self.env['res.partner']._find_accounting_partner(
inv.partner_id)
line = [(0, 0, self.line_get_convert(l, part.id)) for l in iml]
line = inv.group_lines(iml, line)
line = inv.finalize_invoice_move_lines(line)
date = inv.date or inv.date_invoice
move_vals = {
'ref': inv.reference,
'line_ids': line,
'journal_id': inv.journal_id.id,
'date': date,
'narration': inv.comment,
}
move = account_move.create(move_vals)
# Pass invoice in method post: used if you want to get the same
# account move reference when creating the same invoice
# after a cancelled one:
move.post(invoice = inv)
# make the invoice point to that move
vals = {
'move_id': move.id,
'date': date,
'move_name': move.name,
}
inv.write(vals)
return True
@api.one
def _get_outstanding_info_JSON(self):
super(ScopAccountInvoice, self)._get_outstanding_info_JSON()
info = json.loads(self.outstanding_credits_debits_widget)
if info:
values = info.get('content', False)
if self.state == 'open' and values:
domain = [('account_id', '=', self.account_id.id),
('partner_id', '=',
self.env['res.partner']._find_accounting_partner(
self.partner_id).id),
('reconciled', '=', False),
('move_id.state', '=', 'posted'),
'|',
'&', ('amount_residual_currency', '!=', 0.0),
('currency_id', '!=', None),
'&', ('amount_residual_currency', '=', 0.0), '&',
('currency_id', '=', None),
('amount_residual', '!=', 0.0)]
if self.type in ('out_invoice', 'in_refund'):
domain.extend([('credit', '>', 0), ('debit', '=', 0)])
else:
domain.extend([('credit', '=', 0), ('debit', '>', 0)])
lines = self.env['account.move.line'].search(domain)
for value in values:
for line in lines:
if value.get('id') == line.id:
value.update({
'date_maturity': datetime.strftime(line.date_maturity, "%d/%m/%Y"),
'invoice': line.invoice_id.number
})
self.outstanding_credits_debits_widget = json.dumps(info)
# ------------------------------------------------------
# Common Function
# ------------------------------------------------------
@api.multi
def set_scop_contribution(self):
"""
Création d'une ligne dans scop.contribution
"""
self.ensure_one()
if self.is_contribution:
year = self.year
# Get existing contribution for this year
contrib_id = self.env['scop.contribution'].search([
('partner_id', '=', self.partner_id.id),
('year', '=', year),
('type_id', '=', self.type_contribution_id.id)
])
# Create scop.contribution line if not exists
# for year, partner and type
if not contrib_id:
contrib_line = self.env['scop.contribution'].create({
'partner_id': self.partner_id.id,
'type_id': self.type_contribution_id.id,
'year': self.year,
'calculation_date': fields.Datetime.now(),
'amount_calculated': self.amount_total,
'amount_called': self.amount_total,
'spreading': self.nb_quarter,
'invoice_id': self.id,
})
else:
contrib_line = self.set_scop_contribution_hook(contrib_id)
self.contribution_id = contrib_line.id
return contrib_line
return False
def set_scop_contribution_hook(self, contrib_id):
"""
Function that can be inherited if a contrib line already exists
:param contrib_id: scop.contribution line
:return: scop.contribution
"""
return contrib_id
# © 2020 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json
from odoo import api, fields, models
class ScopAccountMove(models.Model):
_inherit = "account.move"
@api.model
def default_get(self, fields):
"""
Attribue la valeur du journal des cotisations par défaut si la facture
est de type 'is_contribution'
Affecte le type 'is_contribution' par défaut si la facture est sur le
journal des cotisations
:param fields:
:return:
"""
res = super(ScopAccountMove, self).default_get(fields)
if res.get("is_contribution"):
res["journal_id"] = self.env.company.contribution_journal_id.id
else:
if res.get("journal_id") == self.env.company.contribution_journal_id.id:
res["is_contribution"] = True
return res
liasse_fiscale_id = fields.Many2one(
comodel_name="scop.liasse.fiscale", string="Liasse Fiscale"
)
year = fields.Integer("Année de cotisation")
is_contribution = fields.Boolean("Cotisation", default=False)
type_contribution_id = fields.Many2one(
comodel_name="scop.contribution.type",
string="Type de cotisation",
readonly=True,
)
partner_ur_id = fields.Many2one(
comodel_name="union.regionale",
string="UR Adhérent",
related="partner_id.ur_id",
store=True,
)
partner_member_number = fields.Char(
string="N° Adhérent",
related="partner_id.member_number",
)
is_sdd = fields.Boolean(
"Au prélèvement", compute="_compute_is_sdd", search="_search_is_sdd"
)
# ------------------------------------------------------
# Computed field
# ------------------------------------------------------
def _compute_is_sdd(self):
sdd_id = self.env.ref("account_banking_sepa_direct_debit.sepa_direct_debit").id
for invoice in self:
if (
invoice.payment_mode_id
and invoice.payment_mode_id.payment_method_id.id == sdd_id
):
invoice.is_sdd = True
else:
invoice.is_sdd = False
def _search_is_sdd(self, operator, value):
recs = self.search([]).filtered(lambda x: x.is_sdd is True)
if recs:
return [("id", "in", [x.id for x in recs])]
# ------------------------------------------------------
# Override Parent
# ------------------------------------------------------
def _compute_payments_widget_to_reconcile_info(self):
"""
Modifie les données de sortie du JSON de réconciliation pour ajouter
la date d'échéance
"""
super()._compute_payments_widget_to_reconcile_info()
for move in self:
info = json.loads(move.invoice_outstanding_credits_debits_widget)
if info:
lines_info = info["content"]
for line in lines_info:
line_id = move.line_ids.browse(line.get("id"))
line.update(
{"date_maturity": fields.Date.to_string(line_id.date_maturity)}
)
move.invoice_outstanding_credits_debits_widget = json.dumps(info)
# Copyright 2020 Le Filament
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models, api
from itertools import groupby
from odoo import _, fields, models
from odoo.exceptions import UserError, ValidationError
class AccountPaymentOrder(models.Model):
_inherit = 'account.payment.order'
_inherit = "account.payment.order"
payment_line_amount = fields.Float(
string='Total Opérations',
compute='_compute_payment_line_amount'
string="Total Opérations", compute="_compute_payment_line_amount"
)
bank_line_amount = fields.Float(
string='Total Lignes de paiement',
compute='_compute_bank_line_amount'
string="Total Lignes de paiement", compute="_compute_bank_line_amount"
)
attachment_ids = fields.One2many(
comodel_name='ir.attachment',
compute='_compute_attachment_ids'
comodel_name="ir.attachment", compute="_compute_attachment_ids"
)
mandate_validity = fields.Boolean(
"Mandats valides", compute="_compute_mandate_validity"
)
email_sent = fields.Boolean("Email envoyé aux coopératives", default=False)
email_datetime = fields.Datetime("Date email envoyé aux coopératives")
email_count = fields.Integer(string="Nombre de mails", compute="_compute_emails")
# ------------------------------------------------------
# Compute fields
# ------------------------------------------------------
@api.multi
def _compute_payment_line_amount(self):
for po in self:
po.payment_line_amount = sum(
po.payment_line_ids.mapped('amount_currency')
)
po.payment_line_amount = sum(po.payment_line_ids.mapped("amount_currency"))
@api.multi
def _compute_bank_line_amount(self):
for po in self:
po.bank_line_amount = sum(
po.bank_line_ids.mapped('amount_currency')
po.payment_ids.filtered(lambda p: not p.reversed_entry_id).mapped(
"amount_total_signed"
)
)
@api.multi
def _compute_attachment_ids(self):
Attachment = self.env['ir.attachment']
Attachment = self.env["ir.attachment"]
for po in self:
po.attachment_ids = Attachment.search([
('res_model', '=', 'account.payment.order'),
('res_id', '=', po.id)
])
po.attachment_ids = Attachment.search(
[
("res_model", "=", "account.payment.order"),
("res_id", "=", po.id),
]
)
def _compute_mandate_validity(self):
for o in self:
validity = o.mapped("payment_line_ids.mandate_id").filtered(
lambda m: m.state != "valid"
)
if validity:
o.mandate_validity = False
else:
o.mandate_validity = True
# Emails
def _compute_emails(self):
for r in self:
email_ids = self.env["mail.mail"].search(
[("mail_message_id", "in", r.message_ids.ids)]
)
r.email_count = len(email_ids)
# ------------------------------------------------------
# Button function
# ------------------------------------------------------
def view_payment_line(self):
tree_id = self.env.ref(
'cgscop_cotisation.scop_account_payment_line_tree').id
tree_id = self.env.ref("cgscop_cotisation.scop_account_payment_line_tree").id
search_id = self.env.ref(
'cgscop_cotisation.scop_account_payment_line_search').id
"cgscop_cotisation.scop_account_payment_line_search"
).id
return {
'type': 'ir.actions.act_window',
'name': "Lignes d'opérations",
'res_model': 'account.payment.line',
'views': [[tree_id, 'tree']],
'search_view_id': [search_id, 'search'],
'domain': [['order_id', '=', self.id]],
"type": "ir.actions.act_window",
"name": "Lignes d'opérations",
"res_model": "account.payment.line",
"views": [[tree_id, "tree"]],
"search_view_id": [search_id, "search"],
"domain": [["order_id", "=", self.id]],
}
def view_account_move(self):
tree_id = self.env.ref(
'cgscop_cotisation.scop_account_move_tree').id
search_id = self.env.ref(
'cgscop_cotisation.scop_account_move_search').id
tree_id = self.env.ref("cgscop_cotisation.scop_account_move_tree").id
search_id = self.env.ref("cgscop_cotisation.scop_account_move_search").id
return {
"type": "ir.actions.act_window",
"name": "Pièces comptables de l'ordre de prélèvement",
"res_model": "account.move",
"views": [[tree_id, "tree"], [False, "form"]],
"search_view_id": [search_id, "search"],
"domain": [["payment_order_id", "=", self.id]],
}
def view_wrong_iban(self):
self.ensure_one()
bank_ids = self.mapped("payment_line_ids.partner_bank_id").filtered(
lambda b: b.acc_type != "iban"
)
return {
"type": "ir.actions.act_window",
"name": "Comptes bancaires",
"res_model": "res.partner.bank",
"views": [[False, "tree"], [False, "form"]],
"domain": [["id", "in", bank_ids.ids]],
}
def view_wrong_mandate(self):
self.ensure_one()
mandate_ids = self.mapped("payment_line_ids.mandate_id").filtered(
lambda m: m.state != "valid"
)
return {
"type": "ir.actions.act_window",
"name": "Mandats non valides",
"res_model": "account.banking.mandate",
"views": [[False, "tree"], [False, "form"]],
"domain": [["id", "in", mandate_ids.ids]],
}
def action_send_email(self):
self.ensure_one()
try:
template = self.env.ref("cgscop_cotisation.email_template_payment_order")
except Exception:
raise UserError(_("Aucun modèle d'e-mail n'a été trouvé."))
# Get partners
partner_ids = self.payment_line_ids.mapped("partner_id")
for partner in partner_ids:
template.with_context(partner_id=partner.id).send_mail(
self.id,
)
self.email_sent = True
self.email_datetime = fields.Datetime.now()
def action_show_emails(self):
return {
'type': 'ir.actions.act_window',
'name': "Pièces comptables de l'ordre de prélèvement",
'res_model': 'account.move',
'views': [[tree_id, 'tree'], [False, 'form']],
'search_view_id': [search_id, 'search'],
'domain': [['payment_order_id', '=', self.id]],
"name": "Etat des mails envoyés",
"type": "ir.actions.act_window",
"view_mode": "tree",
"views": [(False, "tree"), (False, "form")],
"res_model": "mail.mail",
"domain": [("mail_message_id", "in", self.message_ids.ids)],
}
# ------------------------------------------------------
# Common function
# ------------------------------------------------------
def check_sepa_order(self):
for order in self:
if not order.sepa:
msg = (
"Les comptes bancaires des coopératives "
"suivantes ne sont pas corrects : \n"
)
payment_line_ids = order.payment_line_ids.mapped("partner_bank_id")
account_ids = payment_line_ids.filtered(lambda a: a.acc_type != "iban")
for acc in account_ids:
msg += " - " + acc.partner_id.name + " - " + acc.acc_number + "\n"
msg += (
"\nVeuillez corriger ces comptes bancaires pour "
"pouvoir valider l'ordre de prélèvement."
)
raise ValidationError(msg)
else:
return True
# Email
def get_email_values(self):
self.ensure_one()
partner_id = self.env["res.partner"].browse(self.env.context.get("partner_id"))
partner_line_ids = self.payment_line_ids.filtered(
lambda l: l.partner_id == partner_id
)
quarter_detail = []
quarter_agg = []
for line in partner_line_ids:
invoice_id = line.move_line_id.move_id
quarter_detail.append(
("%s-%s" % (invoice_id.year, invoice_id.cotiz_quarter), line.amount_currency)
)
quarter_detail.sort(key=lambda x: x[0])
for quarter, values in groupby(quarter_detail, key=lambda x: x[0]):
amount = 0
for val in values:
amount += val[1]
quarter_agg.append({
"year": quarter[0:4],
"quarter": quarter[-1],
"amount": amount,
})
return {
"partner_id": partner_id,
"total": sum(partner_line_ids.mapped("amount_currency")),
"date": partner_line_ids.mapped("date")[0],
"bank": partner_line_ids.mapped("partner_bank_id.acc_number")[0],
"deadline": quarter_agg,
}
def get_recipients(self):
recipients = ",".join(map(lambda x: str(x), self._get_recipient().ids))
return recipients
def _get_recipient(self):
partner_id = self.env["res.partner"].browse(self.env.context.get("partner_id"))
if partner_id:
tag_cotiz_id = self.env.company.tag_cotiz_id
child_ids = partner_id.child_ids.filtered(
lambda child: (tag_cotiz_id in child.category_id) and child.email
)
if partner_id.email:
recipient_ids = partner_id + child_ids
else:
recipient_ids = child_ids
return recipient_ids
# ------------------------------------------------------
# Override Parent
# ------------------------------------------------------
def open2generated(self):
self.check_sepa_order()
return super(AccountPaymentOrder, self).open2generated()
def _prepare_move(self, bank_lines=None):
res = super()._prepare_move(bank_lines=bank_lines)
res["commercial_partner_id"] = bank_lines[0].partner_id.id
res["partner_id"] = bank_lines[0].partner_id.id
return res
# Copyright 2020 Le Filament
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models, api
class AccountPaymentTerm(models.Model):
_inherit = 'account.payment.term'
is_contribution = fields.Boolean('Conditions de paiement des cotisations')
def compute(self, value, date_ref=False, invoice=False):
"""
Override la fonction compute du modèle account.payment.term
La fonction initiale checke les conditions de paiement et crée
les lignes de paiement associées
L'héritage permet de créer un échéancier en fonction de celui
défini sur la base de cotisations
"""
date_ref = date_ref or fields.Date.today()
amount = value
sign = value < 0 and -1 or 1
if self.env.context.get('currency_id'):
currency = self.env['res.currency'].browse(
self.env.context['currency_id'])
else:
currency = self.env.user.company_id.currency_id
# si base de cotisation
if self.is_contribution and invoice and (invoice.cotisation_cg_id or invoice.cotisation_aura_id):
result = []
if invoice.cotisation_cg_id:
base_contrib_field = 'cotisation_cg_id'
elif invoice.cotisation_aura_id:
base_contrib_field = 'cotisation_aura_id'
trimesters = {
4: invoice[base_contrib_field].trimester_1,
3: invoice[base_contrib_field].trimester_2,
2: invoice[base_contrib_field].trimester_3,
1: invoice[base_contrib_field].trimester_4,
}
for i in range(invoice.nb_quarter, 0, -1):
# Gestion de l'arrondi de la division
if i == 1:
amt = currency.round(amount)
else:
amt = currency.round(value / invoice.nb_quarter)
result.append((fields.Date.to_string(trimesters.get(i)), amt))
amount -= amt
return [result]
else:
return super(AccountPaymentTerm, self).compute(value, date_ref)
......@@ -9,13 +9,17 @@ class CotisationChartTemplate(models.Model):
@api.model
def generate_journals(self, acc_template_ref, company, journals_dict=None):
journal_to_add = [{
'name': 'Cotisation',
'type': 'sale',
'code': 'COT',
'favorite': False,
'sequence': 6}]
journal_to_add = [
{
"name": "Cotisation",
"type": "sale",
"code": "COT",
"favorite": False,
"sequence": 6,
}
]
return super(CotisationChartTemplate, self).generate_journals(
acc_template_ref=acc_template_ref,
company=company,
journals_dict=journal_to_add)
journals_dict=journal_to_add,
)