first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import csv
import io
import re
import zipfile
from collections import defaultdict
from decimal import Decimal
from enum import IntFlag
from sql.operators import Equal
from trytond.model import Exclude, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval
_ACT_FIELDNAMES = [
'DOCTYPE', 'DBKCODE', 'DBKTYPE', 'DOCNUMBER', 'DOCORDER', 'OPCODE',
'ACCOUNTGL', 'ACCOUNTRP', 'BOOKYEAR', 'PERIOD', 'DATE', 'DATEDOC',
'DUEDATE', 'COMMENT', 'COMMENTEXT', 'AMOUNT', 'AMOUNTEUR', 'VATBASE',
'VATCODE', 'CURRAMOUNT', 'CURRCODE', 'CUREURBASE', 'VATTAX', 'VATIMPUT',
'CURRATE', 'REMINDLEV', 'MATCHNO', 'OLDDATE', 'ISMATCHED', 'ISLOCKED',
'ISIMPORTED', 'ISIMPORTED', 'ISTEMP', 'MEMOTYPE', 'ISDOC', 'DOCSTATUS']
def _format_date(date):
if date:
return date.strftime('%Y%m%d')
else:
return ''
class DOCTYPE(IntFlag):
CUSTOMER = 1
SUPPLIER = 2
GENERAL = 3
VAT_0 = 4
class DBKTYPE(IntFlag):
IN_INVOICE = 0
IN_CREDIT_NOTE = 1
OUT_INVOICE = 2
OUT_CREDIT_NOTE = 3
STATEMENT = 4
MISC = 5
class Account(metaclass=PoolMeta):
__name__ = 'account.account'
winbooks_code = fields.Char("WinBooks Code", size=8)
class FiscalYear(metaclass=PoolMeta):
__name__ = 'account.fiscalyear'
winbooks_code = fields.Char("WinBooks Code", size=1)
@classmethod
def copy(cls, fiscalyears, default=None):
default = default.copy() if default is not None else {}
default.setdefault('winbooks_code')
return super().copy(fiscalyears, default=default)
class RenewFiscalYear(metaclass=PoolMeta):
__name__ = 'account.fiscalyear.renew'
def fiscalyear_defaults(self):
defaults = super().fiscalyear_defaults()
if self.start.previous_fiscalyear.winbooks_code:
if self.start.previous_fiscalyear.winbooks_code == '9':
code = 'A'
elif self.start.previous_fiscalyear.winbooks_code == 'Z':
code = None
else:
i = ord(self.start.previous_fiscalyear.winbooks_code)
code = chr(i + 1)
defaults['winbooks_code'] = code
return defaults
class Period(metaclass=PoolMeta):
__name__ = 'account.period'
@property
def winbooks_code(self):
if self.type == 'standard':
periods = [
p for p in self.fiscalyear.periods if p.type == self.type]
i = periods.index(self) + 1
else:
middle_year = (
self.fiscalyear.start_date
+ (self.fiscalyear.end_date
- self.fiscalyear.start_date) / 2)
middle_period = (
self.start_date + (self.end_date - self.start_date) / 2)
i = 0 if middle_period < middle_year else 99
return f'{i:02}'
class Journal(metaclass=PoolMeta):
__name__ = 'account.journal'
winbooks_code = fields.Char("WinBooks Code", size=6)
winbooks_code_credit_note = fields.Char(
"WinBooks Code Credit Note",
states={
'invisible': ~Eval('type').in_(['revenue', 'expense']),
},
help="The code to use for credit note.\n"
"Leave empty to use the default code.")
def get_winbooks_code(self, dbktype):
code = self.winbooks_code
if (dbktype in {DBKTYPE.IN_CREDIT_NOTE, DBKTYPE.OUT_CREDIT_NOTE}
and self.winbooks_code_credit_note):
code = self.winbooks_code_credit_note
return code
class Move(metaclass=PoolMeta):
__name__ = 'account.move'
@property
def winbooks_number(self):
pool = Pool()
Invoice = pool.get('account.invoice')
if isinstance(self.origin, Invoice):
number = self.origin.number
else:
number = self.number
return re.sub(r'[^0-9]', '', number)
class MoveLine(metaclass=PoolMeta):
__name__ = 'account.move.line'
@property
def winbooks_comment(self):
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
if isinstance(self.origin, InvoiceLine):
comment = self.origin.rec_name
else:
comment = self.description_used or self.move_description_used or ''
return comment
class TaxTemplate(metaclass=PoolMeta):
__name__ = 'account.tax.template'
winbooks_code = fields.Char("WinBooks Code", size=8)
def _get_tax_value(self, tax=None):
value = super()._get_tax_value(tax=tax)
if not tax or tax.winbooks_code != self.winbooks_code:
value['winbooks_code'] = self.winbooks_code
return value
class Tax(metaclass=PoolMeta):
__name__ = 'account.tax'
winbooks_code = fields.Char(
"WinBooks Code", size=10,
states={
'readonly': (
Bool(Eval('template', -1)
& ~Eval('template_override', False))),
})
class TaxLine(metaclass=PoolMeta):
__name__ = 'account.tax.line'
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('winbooks_tax_move_line_exclude', Exclude(
t, (t.move_line, Equal),
where=(t.type == 'tax')),
'account_export_winbooks.'
'msg_account_tax_line_tax_move_line_unique'),
]
class MoveExport(metaclass=PoolMeta):
__name__ = 'account.move.export'
@classmethod
def __setup__(cls):
super().__setup__()
cls.type.selection.append(('winbooks', "WinBooks"))
def get_filename(self, name):
name = super().get_filename(name)
if self.type == 'winbooks':
name = f'{self.rec_name}-winbooks.zip'
return name
def _process_winbooks(self):
data = io.BytesIO()
with zipfile.ZipFile(data, 'w') as file:
file.writestr(
'ACT.txt', self._process_winbooks_act(_ACT_FIELDNAMES))
self.file = data.getvalue()
def _process_winbooks_act(
self, fieldnames, dialect='excel', delimiter=',', **fmtparams):
data = io.BytesIO()
writer = csv.DictWriter(
io.TextIOWrapper(data, encoding='utf-8', write_through=True),
fieldnames, extrasaction='ignore',
dialect=dialect, delimiter=delimiter, quoting=csv.QUOTE_ALL,
**fmtparams)
writer.writerows(self._process_winbooks_act_rows())
return data.getvalue()
def _process_winbooks_act_rows(self):
for move in self.moves:
yield from self._process_winbooks_act_move(move)
def _process_winbooks_act_move(self, move):
pool = Pool()
Invoice = pool.get('account.invoice')
try:
Statement = pool.get('account.statement')
except KeyError:
Statement = None
bases = defaultdict(Decimal)
taxes = defaultdict(Decimal)
for line in move.lines:
for tax_line in line.tax_lines:
if tax_line.type == 'base':
bases[tax_line.tax] += tax_line.amount
else:
taxes[tax_line.tax] += tax_line.amount
for line in move.lines:
dbktype = ''
if isinstance(move.origin, Invoice):
invoice = move.origin
sequence_field = f'{invoice.type}_{invoice.sequence_type}'
dbktype = getattr(DBKTYPE, sequence_field.upper())
elif isinstance(move.origin, Statement):
dbktype = DBKTYPE.STATEMENT
row = self._process_winbooks_act_line(line)
move_row = {
'DBKCODE': move.journal.get_winbooks_code(dbktype),
'DBKTYPE': int(dbktype),
'DOCNUMBER': move.winbooks_number,
'BOOKYEAR': move.period.fiscalyear.winbooks_code,
'PERIOD': move.period.winbooks_code,
'DATEDOC': _format_date(move.date),
}
row.update(move_row)
is_credit_note = (
dbktype in {DBKTYPE.IN_CREDIT_NOTE, DBKTYPE.OUT_CREDIT_NOTE})
if row['DOCTYPE'] == DOCTYPE.GENERAL:
vatcode = ''
vatbase = 0
tax = None
for tax_line in line.tax_lines:
if (tax_line.type == 'tax'
and tax_line.tax.winbooks_code):
tax = tax_line.tax
vatbase += bases[tax_line.tax]
break
else:
tax_line = None
if tax_line:
if taxes[tax] != tax_line.amount:
vatbase *= round(tax_line.amount / taxes[tax], 3)
vatcode = tax.winbooks_code
if is_credit_note:
vatbase *= -1
row.setdefault('VATBASE', vatbase)
row.setdefault('VATCODE', vatcode)
if row['DOCTYPE'] == DOCTYPE.GENERAL:
for tax_line in line.tax_lines:
tax = tax_line.tax
if (tax_line.type == 'base'
and tax.type == 'percentage' and not tax.rate):
vatcode = tax.winbooks_code
vatbase = tax_line.amount
if is_credit_note:
vatbase *= -1
vat_0_row = {
'DOCTYPE': int(DOCTYPE.VAT_0),
'COMMENT': tax.description,
'AMOUNT': 0,
'AMOUNTEUR': 0,
'VATBASE': vatbase,
'VATCODE': vatcode,
}
vat_0_row.update(move_row)
yield vat_0_row
yield row
def _process_winbooks_act_line(self, line):
accountrp = ''
if line.account.type.receivable and line.party:
doctype = DOCTYPE.CUSTOMER
if identifier := line.party.winbooks_customer_identifier:
accountrp = identifier.code
elif line.account.type.payable and line.party:
doctype = DOCTYPE.SUPPLIER
if identifier := line.party.winbooks_supplier_identifier:
accountrp = identifier.code
else:
doctype = DOCTYPE.GENERAL
return {
'DOCTYPE': int(doctype),
'ACCOUNTGL': (
line.account.winbooks_code or line.account.code),
'ACCOUNTRP': accountrp,
'DUEDATE': _format_date(line.maturity_date),
'COMMENT': line.winbooks_comment[:40],
'AMOUNT': 0,
'AMOUNTEUR': line.debit - line.credit,
'CURRAMOUNT': line.amount_second_currency,
'CURRCODE': (
line.second_currency.code if line.second_currency
else ''),
}