Skip to content

Commit 875dff4

Browse files
committed
[MIG] datev_export_dtvf: Migration to 18.0
1 parent c3d4c99 commit 875dff4

File tree

12 files changed

+221
-67
lines changed

12 files changed

+221
-67
lines changed

datev_export_dtvf/__manifest__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"name": "DATEV",
55
"summary": "Export Data for DATEV (dtvf)",
6-
"version": "15.0.2.0.0",
6+
"version": "18.0.1.0.0",
77
"development_status": "Beta",
88
"category": "Accounting",
99
"website": "https://github.com/OCA/l10n-germany",
@@ -20,6 +20,7 @@
2020
"security/ir.model.access.csv",
2121
"views/account_account.xml",
2222
"views/datev_export_dtvf.xml",
23+
"views/res_config_settings.xml",
2324
"views/res_partner.xml",
2425
],
2526
}

datev_export_dtvf/data/ir_cron.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<field name="name">Monthly DATEV export via mail (dtvf)</field>
44
<field name="interval_number">1</field>
55
<field name="interval_type">months</field>
6-
<field name="numbercall">-1</field>
76
<field name="model_id" ref="model_datev_export_dtvf_export" />
87
<field name="active" eval="False" />
98
<field name="code">

datev_export_dtvf/datev.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def _coerce_value(self, field, value):
128128
if field.length:
129129
value = str(value)[: field.length]
130130
if field.quote:
131-
value = '"%s"' % str(value).replace('"', '""')
131+
value = f'''"{str(value).replace('"', '""')}"'''
132132
return str(value)
133133

134134
def writerow(self, row):
@@ -165,7 +165,7 @@ def __init__(
165165
account_code_length,
166166
period_start,
167167
period_end,
168-
"Buchungsstapel %s" % period_start,
168+
f"Buchungsstapel {period_start}",
169169
user_initials,
170170
currency,
171171
[

datev_export_dtvf/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
from . import account_account
44
from . import datev_export_dtvf
5+
from . import res_company
6+
from . import res_config_settings
57
from . import res_partner

datev_export_dtvf/models/account_account.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ class AccountAccount(models.Model):
1313
"in DATEV.",
1414
)
1515
datev_code = fields.Char(
16-
help="In case your COA codes don't work for DATEV, fill in an alternative code here"
16+
help="In case your COA codes don't work for DATEV, fill in an alternative code "
17+
"here"
1718
)

datev_export_dtvf/models/datev_export_dtvf.py

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,17 @@ class DatevExportDtvfExport(models.Model):
2222
fiscalyear_id = fields.Many2one(
2323
"date.range",
2424
string="Fiscal year",
25-
states={"draft": [("required", True), ("readonly", False)]},
26-
readonly=True,
27-
)
28-
name = fields.Char(
29-
states={"draft": [("required", True), ("readonly", False)]},
30-
readonly=True,
3125
)
26+
name = fields.Char()
3227
fiscalyear_start = fields.Date(related="fiscalyear_id.date_start")
3328
fiscalyear_end = fields.Date(related="fiscalyear_id.date_end")
3429
period_ids = fields.Many2many(
3530
"date.range",
3631
string="Periods",
37-
states={"draft": [("required", True), ("readonly", False)]},
38-
readonly=True,
3932
)
4033
journal_ids = fields.Many2many(
4134
"account.journal",
4235
string="Journals",
43-
states={"draft": [("readonly", False)]},
44-
readonly=True,
4536
)
4637
date_generated = fields.Datetime("Generated at", readonly=True, copy=False)
4738
file_data = fields.Binary("Data", readonly=True, copy=False)
@@ -123,9 +114,8 @@ def action_generate(self):
123114
for move in moves:
124115
writer.writerows(this._get_data_transaction(move))
125116

126-
filename = (
127-
"EXTF_Buchungsstapel_%s.csv"
128-
% date_range.date_start.strftime("%Y%m%d")
117+
filename = "EXTF_Buchungsstapel_{}.csv".format(
118+
date_range.date_start.strftime("%Y%m%d")
129119
)
130120
zip_file.writestr(filename, writer.buffer.getvalue())
131121

@@ -146,7 +136,7 @@ def action_generate(self):
146136

147137
accounts = self.env["account.account"].search(
148138
[
149-
("company_id", "=", this.company_id.id),
139+
("company_ids", "=", this.company_id.id),
150140
]
151141
)
152142
writer = DatevAccountWriter(
@@ -178,7 +168,8 @@ def _get_data_transaction(self, move):
178168
move_line2amount = {
179169
move_line: move_line.credit or move_line.debit
180170
for move_line in move.line_ids
181-
if not move_line.display_type and (move_line.debit or move_line.credit)
171+
if not move_line.display_type.startswith("line_")
172+
and (move_line.debit or move_line.credit)
182173
}
183174
currency = move.currency_id or move.company_id.currency_id
184175
code_length = move.company_id.datev_account_code_length
@@ -199,10 +190,13 @@ def _get_data_transaction(self, move):
199190
if currency.is_zero(move_line2amount[move_line2]):
200191
move_line2amount.pop(move_line2)
201192
break
202-
if move_line.account_id.internal_type not in (
203-
"receivable",
204-
"payable",
205-
) and move_line2.account_id.internal_type in ("receivable", "payable"):
193+
if move_line.account_id.account_type not in (
194+
"asset_receivable",
195+
"liability_payable",
196+
) and move_line2.account_id.account_type in (
197+
"asset_receivable",
198+
"liability_payable",
199+
):
206200
move_line, move_line2 = move_line2, move_line
207201
if move_line.account_id.datev_export_nonautomatic:
208202
move_line, move_line2 = move_line2, move_line
@@ -215,10 +209,10 @@ def _get_data_transaction(self, move):
215209
)
216210
number_type = (
217211
"customer"
218-
if ml.account_id.internal_type == "receivable"
212+
if ml.account_id.account_type == "asset_receivable"
219213
else (
220214
"supplier"
221-
if ml.account_id.internal_type == "payable"
215+
if ml.account_id.account_type == "liability_payable"
222216
else None
223217
)
224218
)
@@ -231,7 +225,7 @@ def _get_data_transaction(self, move):
231225
else:
232226
offset_account_number = number
233227
data = {
234-
"Umsatz (ohne Soll/Haben-Kz)": ("%.2f" % abs(amount)).replace(".", ","),
228+
"Umsatz (ohne Soll/Haben-Kz)": f"{abs(amount):.2f}".replace(".", ","),
235229
"Soll/Haben-Kennzeichen": move_line.debit and "S" or "H",
236230
"Konto": account_number,
237231
"Gegenkonto (ohne BU-Schlüssel)": offset_account_number,
@@ -259,33 +253,28 @@ def _get_data_transaction(self, move):
259253
)[:1].name
260254
or move.name,
261255
"Belegfeld 2": move_line2.name,
262-
"KOST1 - Kostenstelle": move_line.analytic_account_id.code
263-
or move_line.analytic_account_id.name
264-
or move_line2.analytic_account_id.code
265-
or move_line2.analytic_account_id.name,
256+
"KOST1 - Kostenstelle": move_line.analytic_line_ids[:1].account_id.code
257+
or move_line.analytic_line_ids[:1].account_id.name
258+
or move_line2.analytic_line_ids[:1].account_id.code
259+
or move_line2.analytic_line_ids[:1].account_id.name,
266260
"KOST-Datum": move.date.strftime("%d%m%Y"),
267261
}
268262
if move_line.amount_currency:
269263
factor = abs(amount / (move_line.debit or move_line.credit))
264+
rate = 1 / currency._get_conversion_rate(
265+
move_line.currency_id,
266+
currency,
267+
move.company_id,
268+
move.date,
269+
)
270270
data.update(
271271
{
272272
"Umsatz (ohne Soll/Haben-Kz)": (
273-
"%.2f" % abs(move_line.amount_currency * factor)
273+
f"{abs(move_line.amount_currency * factor):.2f}"
274274
).replace(".", ","),
275275
"WKZ Umsatz": move_line.currency_id.name,
276-
"Kurs": (
277-
"%.6f"
278-
% (
279-
1
280-
/ currency._get_conversion_rate(
281-
move_line.currency_id,
282-
currency,
283-
move.company_id,
284-
move.date,
285-
)
286-
)
287-
).replace(".", ","),
288-
"Basis-Umsatz": ("%.2f" % abs(amount)).replace(".", ","),
276+
"Kurs": f"{rate:.6f}".replace(".", ","),
277+
"Basis-Umsatz": f"{abs(amount):.2f}".replace(".", ","),
289278
"WKZ Basis-Umsatz": currency.name,
290279
}
291280
)
@@ -337,26 +326,27 @@ def _get_data_partner(self, partner):
337326
def _get_data_account(self, account):
338327
yield {
339328
"Konto": account.datev_code
340-
or account.code[-account.company_id.datev_account_code_length :],
329+
or account.code[-self.company_id.datev_account_code_length :],
341330
"Kontobeschriftung": account.name,
342331
"SprachId": self.env.user.lang.replace("_", "-"),
343332
"Kontenbeschriftung lang": account.name,
344333
}
345334

346335
def _get_partner_number(self, partner, number_type, generate=False):
347336
if self.company_id.datev_partner_numbering == "sequence":
348-
field_name = "l10n_de_datev_export_identifier_%s" % number_type
337+
field_name = f"l10n_de_datev_export_identifier_{number_type}"
349338
if not partner[field_name] and generate:
350339
getattr(
351340
partner,
352-
"action_l10n_de_datev_export_identifier_%s" % number_type,
341+
f"action_l10n_de_datev_export_identifier_{number_type}",
353342
)()
354343
return partner[field_name]
355344
elif self.company_id.datev_partner_numbering == "ee":
356345
account_length = self.env["account.general.ledger"]._get_account_length()
357346
return partner[
358-
"l10n_de_datev_identifier%s"
359-
% ("_customer" if number_type == "customer" else "")
347+
"l10n_de_datev_identifier{}".format(
348+
"_customer" if number_type == "customer" else ""
349+
)
360350
] or str(
361351
(1 if number_type == "customer" else 7) * 10**account_length
362352
+ partner.id
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2026 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
from odoo import fields, models
4+
5+
6+
class ResCompany(models.Model):
7+
_inherit = "res.company"
8+
9+
datev_account_code_length = fields.Integer(
10+
"DATEV account code length",
11+
default=5,
12+
)
13+
14+
datev_partner_numbering = fields.Selection(
15+
string="DATEV Partner numbering",
16+
selection="_selection_datev_partner_numbering",
17+
default="none",
18+
)
19+
20+
datev_customer_sequence_id = fields.Many2one(
21+
"ir.sequence", "DATEV sequence for customers"
22+
)
23+
24+
datev_supplier_sequence_id = fields.Many2one(
25+
"ir.sequence", "DATEV sequence for suppliers"
26+
)
27+
28+
def _selection_datev_partner_numbering(self):
29+
reports_installed = (
30+
"l10n_de_datev_reports" in self.env["ir.module.module"]._installed()
31+
)
32+
return (
33+
[("none", "None")]
34+
+ ([("ee", "Enterprises Edition")] if reports_installed else [])
35+
+ [("sequence", "Sequence")]
36+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2026 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
from odoo import fields, models
4+
5+
6+
class ResConfigSettings(models.TransientModel):
7+
_inherit = "res.config.settings"
8+
9+
datev_account_code_length = fields.Integer(
10+
related="company_id.datev_account_code_length",
11+
readonly=False,
12+
)
13+
14+
datev_partner_numbering = fields.Selection(
15+
related="company_id.datev_partner_numbering",
16+
readonly=False,
17+
)
18+
19+
datev_customer_sequence_id = fields.Many2one(
20+
related="company_id.datev_customer_sequence_id",
21+
readonly=False,
22+
)
23+
24+
datev_supplier_sequence_id = fields.Many2one(
25+
related="company_id.datev_supplier_sequence_id",
26+
readonly=False,
27+
)

datev_export_dtvf/tests/test_datev_export_dtvf.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
import unittest
77
import zipfile
88

9+
from dateutil.relativedelta import relativedelta
10+
911
from odoo.exceptions import ValidationError
10-
from odoo.tests.common import Form, TransactionCase, can_import
12+
from odoo.tests import Form
13+
from odoo.tests.common import TransactionCase, can_import
1114

1215

1316
class TestDatevExportDtvf(TransactionCase):
@@ -49,14 +52,14 @@ def setUp(self):
4952
{
5053
"name": "Revenue",
5154
"code": "424242",
52-
"user_type_id": self.env.ref("account.data_account_type_revenue").id,
55+
"account_type": "income",
5356
}
5457
)
5558
self.account2 = self.env["account.account"].create(
5659
{
5760
"name": "Receivable",
5861
"code": "424243",
59-
"user_type_id": self.env.ref("account.data_account_type_receivable").id,
62+
"account_type": "asset_receivable",
6063
"reconcile": True,
6164
}
6265
)
@@ -134,7 +137,9 @@ def test_nonautomatic_flag(self):
134137
self.assertIn('"40"', move_line)
135138

136139
def test_move_line_without_account(self):
137-
"""Test that non-accounting (display_type!=False) lines don't crash the export"""
140+
"""
141+
Test that non-accounting (display_type!=False) lines don't crash the export
142+
"""
138143
self.move.write(
139144
{
140145
"line_ids": [
@@ -152,7 +157,7 @@ def test_move_line_without_account(self):
152157
self.move.action_post()
153158
self.wizard.action_generate()
154159

155-
def test_sequence(self):
160+
def test_partner_numbering_sequence(self):
156161
"""Test datev_partner_numbering = sequence"""
157162
self.wizard.company_id.datev_partner_numbering = "sequence"
158163
self.wizard.company_id.datev_customer_sequence_id = self.env[
@@ -187,3 +192,42 @@ def test_ee(self):
187192
self.customer.l10n_de_datev_identifier_customer = "424242"
188193
self.move.action_post()
189194
self.wizard.action_generate()
195+
196+
def test_cron(self):
197+
"""Test the cronjob"""
198+
last_month = self.env["date.range"].create(
199+
{
200+
"name": "month range",
201+
"type_id": self.env["date.range.type"]
202+
.create(
203+
{
204+
"name": "test month type",
205+
}
206+
)
207+
.id,
208+
"date_start": datetime.date.today() - relativedelta(months=1, day=1),
209+
"date_end": datetime.date.today()
210+
- relativedelta(months=1, day=1)
211+
+ relativedelta(months=1, days=-1),
212+
}
213+
)
214+
self.env["date.range"].create(
215+
{
216+
"name": "year range",
217+
"type_id": self.env["date.range.type"]
218+
.create(
219+
{
220+
"name": "test year type",
221+
}
222+
)
223+
.id,
224+
"date_start": last_month.date_start + relativedelta(month=1, day=1),
225+
"date_end": last_month.date_start + relativedelta(month=12, day=31),
226+
}
227+
)
228+
cronjob = self.env.ref("datev_export_dtvf.cron_export")
229+
230+
mails_before = self.env["mail.mail"].search([])
231+
cronjob.ir_actions_server_id.run()
232+
new_mail = self.env["mail.mail"].search([]) - mails_before
233+
self.assertTrue(new_mail)

0 commit comments

Comments
 (0)