From c14374ae47a6fd1ae5b1d6fb477829ad8fce50ff Mon Sep 17 00:00:00 2001 From: abdel karim Date: Mon, 2 Feb 2026 15:14:52 +0200 Subject: [PATCH] AddImportJournalEntries --- account_journal_import/__init__.py | 1 + account_journal_import/__manifest__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 228 bytes account_journal_import/models/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 247 bytes .../__pycache__/account_move.cpython-312.pyc | Bin 0 -> 1128 bytes .../error_table_wizard.cpython-312.pyc | Bin 0 -> 3408 bytes .../misc_import_wizard.cpython-312.pyc | Bin 0 -> 18526 bytes .../models/misc_import_wizard.py | 424 ++++++++++++++++++ .../security/ir.model.access.csv | 4 + .../views/account_move_view.xml | 26 ++ .../views/misc_import_wizard_view.xml | 76 ++++ 12 files changed, 548 insertions(+) create mode 100644 account_journal_import/__init__.py create mode 100644 account_journal_import/__manifest__.py create mode 100644 account_journal_import/__pycache__/__init__.cpython-312.pyc create mode 100644 account_journal_import/models/__init__.py create mode 100644 account_journal_import/models/__pycache__/__init__.cpython-312.pyc create mode 100644 account_journal_import/models/__pycache__/account_move.cpython-312.pyc create mode 100644 account_journal_import/models/__pycache__/error_table_wizard.cpython-312.pyc create mode 100644 account_journal_import/models/__pycache__/misc_import_wizard.cpython-312.pyc create mode 100644 account_journal_import/models/misc_import_wizard.py create mode 100644 account_journal_import/security/ir.model.access.csv create mode 100644 account_journal_import/views/account_move_view.xml create mode 100644 account_journal_import/views/misc_import_wizard_view.xml diff --git a/account_journal_import/__init__.py b/account_journal_import/__init__.py new file mode 100644 index 0000000..9a7e03e --- /dev/null +++ b/account_journal_import/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/account_journal_import/__manifest__.py b/account_journal_import/__manifest__.py new file mode 100644 index 0000000..8764d67 --- /dev/null +++ b/account_journal_import/__manifest__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +{ + "name": "Journal Entry Import (CSV/XLSX) - Misc Operations", + "version": "19.0.1.0.0", + "category": "Accounting", + "summary": "Import journal items into Miscellaneous Operations with validation preview", + "depends": ["account"], + "data": [ + "security/ir.model.access.csv", + "views/account_move_view.xml", + "views/misc_import_wizard_view.xml", + ], + "installable": True, + "application": False, + "license": "LGPL-3", +} diff --git a/account_journal_import/__pycache__/__init__.cpython-312.pyc b/account_journal_import/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07f1fcd91a6452f2ad60601f4f31fdc8c2f5a9b6 GIT binary patch literal 228 zcmX@j%ge<81S`90GDU#&V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zY`OUKLkdF*V-7kWUo^f=#7C7PYt(k%UlR4j#S0ma0RI7AlYnF@*Xv{E6gF>yzd zIWs$3Tq~@aOU1PZg_*p$xO~6(VAWhM&aLI=&3tiYZhmgsycn5nclY>xo#H`jNSy8g z0&IwZtzc-Uu(Atmbt`45{KSmw(pnfV(@oOII*F1CgytS4kV$p%$@*J&1ryl8xNF7A z5l%RTRh-7!HrVK(BAmPAL9D;lR)E!0XbgHR=eV&)!$|&7-3u|TH_k5xRmx&>iPl)? z_~v3vg2=o)yYlerdVXcyYMkj7=tXYMYkTMFUc+ISt7Xapo(f5w_hC|YYJQAlAIQU& z#?^SMO1K(20pS@&B4HA^^US74>TQ{8vBL!ZTz6?0lP9sIFjW-K6%h})8WF!7$)?Re zzB^s2GFo9yV3s|fL?wzTHOFq{u94jMweiWZvGE(Fh_Fq<+NP3Qi((oe2V>E3snZ17 zNZLe6AEjgyx>i|xuDZp~B|srsB_Rq#-yMGen(VuA<=6x}p#INSzK!UrU_0PY1B0Vq z1_lp?ralhbY@VKMo<83kI@>&Dy_tA7^>*rD`1;RYz27(ldd6sp5KyqvyayqPAVSSZ6<{%E7&e*_KoZ;wA4)6NF1f9 zU>RJMm@>I@$ryBBJPa?aF!_wxuwYzA@{BO9dSRLJ^s*dja>-fg)V=sm&+VkxyX=B6 z$SFn>f*qzHgx|rLuVCb}aq`8&M`L71Zvy>!_F49zclQ1ICs6pMW})%o(%$IH(ceIb H?W}(R2RSSD literal 0 HcmV?d00001 diff --git a/account_journal_import/models/__pycache__/error_table_wizard.cpython-312.pyc b/account_journal_import/models/__pycache__/error_table_wizard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb5901e2d58e7097c148be0aecd362b3c223bd7d GIT binary patch literal 3408 zcmaJETWl1`vFEujub-F$Y+z#t@bXw}2tLjjM_j-m#yAo|Lbvj1GT!N3&oaBS=^3;3 zX07Z+C}-y)VJlMhB0#Z{eB?wT@rjgt@EIvTCX19+i&MzmPm-^_gG32msqWcX`x2A3 zTwPODU0q#OUDf-CXf%vqd~&;Dei}gNQ}*ExrHQ=w0U%XmAVaWFPDlzlF)8Mxq?D7B zvcTw~rR3D4%5ljG^^_Pt@(k} zlx#NwSWSyY@V1=nSPI3%e`AceOeMWwQZ;R2%W$Zgvkh!HRM6rARj=yg=0f2kfkx)t zoTXtWr57+TLK*B%+nEd|sjZz5Y8`V9s8xhfQZP_bG=!vNh)EetSF&x}7kIcK8M2}L zT5Xvcz`<`EiK>oE%zTCh9X)TlW(m_E!Mb7Rt)-N>&A1@o z%*QYO4xoz4DBFz9HW`Sw+l&K3uJ0yP7W_E+v)Q67i=au}uEWE#q5q{BqQ8DwG9)+R zOBk|G2VFZkUNO|Nm~Hpvx8}066V*`CVn#3m_X6KS@!$*%b6;vKH%b?pzSgq#0?F%^ z)^ztR6Bjj~$KudrXx>^c4qDnKxl*r55_-xt?YzTgt!U;AyGWv77L}OE&DqD%X^4UT5dWpT=N zG}qQ#ea^!CL{!50TT~)AO;{MHqVfGi(||i8JJ?E7)mu*`I0cpY7BYy&V7wx!vDpH# zGepnD(x!!-S=+Gf*vMOnVVpQMeCo`|$f?m;2a{WvaMo;U!Ex=JrW=rB&aCgUFS>Kq zyPmV2^E_ic7bXfzG~#(rvkN$18oKa*u13?(T#|*~aRF4&^Y-pLH&<`g+xFZWe0pH) z(P-_!+qHdPYe-^W_yrO>A}dlOh&p%OSzKMbQ(i4^_G`6{Z?DJ=74;r}wySr|uI)Np z-@E^3`+mBwzV~2#cVB(?-fDV1U4NsmT3jz~sGGZwZ1xU)))DLsH&8GHT4Bt@BQ&US z|7scyYb>4%5C)C5Y1(%dbc=aj)5rkG0?B|nM9cz96vF&X<;#$Pqz6#KZW&d$mq;A& zj}SS^=tB&!E5HG}v(6}h+o;}_s9dV|3{<9{clEFBuJ*0>ZCrl1@YsFYHC>r{t_0tI z=e>766FZdf3kj(m9P?B%Z7jiD$oJItJI@l9SA>58l~sgA!4U8kyiW8TB_c9c|o z34eaAWWbm3XOL{W-=YfWM<2+@h?Yg5x9{xXvb1e&$9-iv=ytVi4*EQ(j-4~5Lz7Ul zbk~I@5<6tXyjl^1Iu^Tp{pxhgFr9*>FF~h~uGvMZOtJ<{l}oHWmE>`kOi=WNv|_1o z8vb950l0z$o?7oh)XH7T`^zg2gop7c-s+hO1(|=wU1OpYf>RhC8urkWz$$$nW>t9HFmJ^icge!%2Xgx2iFs^h z=H0Q_S56HV7SF|OsJ&^+E{^F7u6=G`g0F#9yspMlmhL#`2fPmsPdK@lhs?931HS5_ zX}I%aBg4ao&c)_zVqkJ2Wm}eBaPXLqoEvDe&JTDiHq~oBKw-6i4yJ9+HkKwz zM}WgI|AZ&`F+9QXli(!}g%?#ov-rb~%4R-oQ^~@4DrapoPbJ<^^#U{(gRoZ!Veuub z4#h*{5KL5M+e73OZU>dwo>YK#%%f^kg-4W1IVVG9_H1yN9u_G+yefT=X=UI`@b7d% zVpmW-+VjKNHBy;;CP&_%es6lS>+J6aAEkeL?5RBYN%U}KvfkFccCC72{RU*!8`o-GLn~^%v#YACD{I!q z+qKRkD@wh+;|^ZMYu^NZ`_PK?JlJ+8vKncKLbT^u&w+c+#^~Me)_RU@b{}sb;Y5>m z=5DFhb9A%&7|@P4X{YZl)_R6EyW>DRx~mb8qr3hgsiANq0){m9Nu3u3=$)5@DdF*( zAaX_Mtao--1M7hgd)6-3!uuE&mk1`V{XanEVW$89 literal 0 HcmV?d00001 diff --git a/account_journal_import/models/__pycache__/misc_import_wizard.cpython-312.pyc b/account_journal_import/models/__pycache__/misc_import_wizard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b945e4c5184a5780d28264580ff18dc604ea42f1 GIT binary patch literal 18526 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 open result wizard + if issues: + return self.env["account.misc.import.result.wizard"].action_open(issues) + + # group by entry number (each number => one move) + groups = defaultdict(list) + for r in rows: + key = _to_str(r.get("number")) or "__no_number__" + groups[key].append(r) + + for number, group_rows in groups.items(): + move = self._create_move(number=number, rows=group_rows) + self._create_move_lines(move, group_rows) + + return { + "type": "ir.actions.act_window", + "name": _("Journal Entries (Miscellaneous)"), + "res_model": "account.move", + "view_mode": "list,form", + "domain": [("move_type", "=", "entry")], + "context": {"search_default_group_by_journal": 1}, + } + + + # Create move + + def _create_move(self, number=None, rows=None): + rows = rows or [{}] + file_date = _to_str(rows[0].get("date")) + journal_id = rows[0].get("_journal_id") + + if not journal_id: + + raise UserError(_("Missing journal for entry '%s'.") % (number or "")) + + return self.env["account.move"].create({ + "move_type": "entry", + "journal_id": journal_id, + "date": file_date or fields.Date.context_today(self), + "ref": number or _("Imported Journal Entry"), + }) + + + # File reading + + def _read_file(self): + raw = base64.b64decode(self.file_data or b"") + name = (self.file_name or "").lower() + + if name.endswith(".csv"): + self.write({"type_file": "CSV"}) + return self._read_csv(raw) + + if name.endswith(".xlsx"): + if not openpyxl: + raise UserError(_("XLSX import requires python library 'openpyxl'.")) + self.write({"type_file": "XLSX"}) + return self._read_xlsx(raw) + + raise UserError(_("Only CSV or XLSX files are supported.")) + + def _read_csv(self, raw): + text = raw.decode("utf-8-sig", errors="ignore") + reader = csv.DictReader(io.StringIO(text)) + + if not reader.fieldnames: + raise UserError(_("CSV must have a header row.")) + + headers = [_to_str(h).lower() for h in reader.fieldnames] + headers_set = set(headers) + + missing = REQUIRED_COLUMNS - headers_set + if missing: + raise UserError(_("Missing columns: %s") % ", ".join(sorted(missing))) + + for pcol in PRODUCT_COLUMNS: + if pcol in headers_set: + raise UserError(_("Product data detected in column '%s' -> not Misc.") % pcol) + + rows = [] + for i, row in enumerate(reader, start=2): + normalized = {(_to_str(k).lower()): row.get(k) for k in row.keys()} + normalized["_row"] = i + rows.append(normalized) + return rows + + def _read_xlsx(self, raw): + wb = openpyxl.load_workbook(io.BytesIO(raw), data_only=True) + ws = wb.active + + headers = [_to_str(c.value).lower() for c in ws[1]] + if not any(headers): + raise UserError(_("XLSX first row must contain headers.")) + + headers_set = set(headers) + missing = REQUIRED_COLUMNS - headers_set + if missing: + raise UserError(_("Missing columns: %s") % ", ".join(sorted(missing))) + + for pcol in PRODUCT_COLUMNS: + if pcol in headers_set: + raise UserError(_("Product data detected in column '%s' -> not Misc.") % pcol) + + rows = [] + for i, values in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2): + row = dict(zip(headers, values)) + row["_row"] = i + rows.append(row) + return rows + + + # Validation + + def _validate_rows(self, rows): + issues = [] + + def add_issue(severity, row_no, field_name, message): + issues.append({ + "severity": severity or "error", + "row_no": int(row_no or 0), + "field_name": field_name or "", + "message": message or "", + }) + + if not rows: + raise UserError(_("The file is empty.")) + + company = self.env.company + Currency = self.env["res.currency"] + Partner = self.env["res.partner"] + Journal = self.env["account.journal"] + Account = self.env["account.account"] + + # group numbers + unique_numbers = set(_to_str(r.get("number")) for r in rows) + + # Must have at least 2 lines per entry number + for num in unique_numbers: + count = sum(1 for r in rows if _to_str(r.get("number")) == num) + if count < 2: + add_issue( + "error", 0, "number", + _("Entry '%(num)s' has only %(count)d line(s). Must be at least 2.") + % {"num": num, "count": count} + ) + + # Validate each entry group + for num in unique_numbers: + entry_rows = [r for r in rows if _to_str(r.get("number")) == num] + + total_debit = 0.0 + total_credit = 0.0 + + + journal_id_for_entry = None + + for r in entry_rows: + row_no = r.get("_row", 0) + + # debit/credit rules + debit = _to_float(r.get("debit")) + credit = _to_float(r.get("credit")) + + if debit < 0 or credit < 0: + add_issue("error", row_no, "debit/credit", _("Debit/Credit cannot be negative.")) + + if debit > 0 and credit > 0: + add_issue("error", row_no, "debit/credit", _("Cannot have both debit and credit.")) + + if debit == 0 and credit == 0: + add_issue("error", row_no, "debit/credit", _("Debit and credit cannot both be zero.")) + + total_debit += debit + total_credit += credit + + # currency check + cur_code = _to_str(r.get("currency")) + if cur_code: + currency = Currency.search( + [("name", "=", cur_code), ("active", "=", True)], + limit=1 + ) + if not currency: + add_issue("error", row_no, "currency", _("Currency '%s' not found.") % cur_code) + + # partner check (optional) - using ref as you do + partner_val = _to_str(r.get("partner")) + if partner_val: + partner = Partner.search([("ref", "=", partner_val)], limit=1) + if partner: + r["_partner_id"] = partner.id + else: + add_issue("error", row_no, "partner", _("Partner '%s' not found.") % partner_val) + + # journal by code (required) + journal_code = _to_str(r.get("journal")) + if not journal_code: + add_issue("error", row_no, "journal", _("Journal is empty.")) + else: + journal = Journal.search( + [("company_id", "=", company.id), ("code", "=", journal_code)], + limit=1 + ) + if not journal: + add_issue("error", row_no, "journal", _("Journal code '%s' not found.") % journal_code) + else: + r["_journal_id"] = journal.id + if journal_id_for_entry is None: + journal_id_for_entry = journal.id + elif journal_id_for_entry != journal.id: + add_issue( + "error", row_no, "journal", + _("Entry '%s' has multiple journals. Use one journal per entry number.") % num + ) + + # account by code (required) + acc_code = _to_str(r.get("account")) + if not acc_code: + add_issue("error", row_no, "account", _("Account is empty.")) + else: + acc = Account.search( + [("company_ids", "in", company.id), ("code", "=", acc_code)], + limit=1 + ) + if not acc: + add_issue("error", row_no, "account", _("Account code '%s' not found.") % acc_code) + else: + r["_account_id"] = acc.id + + # Balanced totals per entry + rounding = company.currency_id.rounding or 0.01 + if abs(total_debit - total_credit) > rounding: + add_issue( + "error", + entry_rows[-1].get("_row", 0), + "balance", + _("Entry '%(n)s' not balanced: debit=%(d)s credit=%(c)s") + % {"n": num, "d": total_debit, "c": total_credit} + ) + + + return issues + + + # Create move lines + + def _create_move_lines(self, move, rows): + aml = self.env["account.move.line"] + + Currency = self.env["res.currency"] + vals_list = [] + + for r in rows: + row_no = r.get("_row", 0) + + account_id = r.get("_account_id") + if not account_id: + # Should not happen if validation is correct + raise UserError(_("Row %s: Account not resolved.") % row_no) + + name = _to_str(r.get("description")) or "/" + debit = _to_float(r.get("debit")) + credit = _to_float(r.get("credit")) + partner_id = r.get("_partner_id") or False + + currency_id = False + cur_code = _to_str(r.get("currency")) + if cur_code: + currency = Currency.search([("name", "=", cur_code)], limit=1) + if currency: + currency_id = currency.id + + amount_currency = 0.0 + if r.get("amount_currency") not in (None, ""): + amount_currency = _to_float(r.get("amount_currency")) + + debit2 = _to_float(r.get("debit2")) if r.get("debit2") not in (None, "") else 0.0 + credit2 = _to_float(r.get("credit2")) if r.get("credit2") not in (None, "") else 0.0 + + vals_list.append({ + "move_id": move.id, + "account_id": account_id, + "name": name, + "debit": debit, + "credit": credit, + "partner_id": partner_id, + "product_id": False, + "currency_id": currency_id, + "amount_currency": amount_currency, + "debit2": debit2, + "credit2": credit2, + }) + + + lines = aml.create(vals_list) + for r, line in zip(rows, lines): + + line.write({ + "debit2": _to_float(r.get("debit2")), + "credit2": _to_float(r.get("credit2")), + "balance2": _to_float(r.get("debit2")) - _to_float(r.get("credit2")), + }) diff --git a/account_journal_import/security/ir.model.access.csv b/account_journal_import/security/ir.model.access.csv new file mode 100644 index 0000000..73005a3 --- /dev/null +++ b/account_journal_import/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_misc_import_wizard,access_account_misc_import_wizard,model_account_misc_import_wizard,account.group_account_user,1,1,1,1 +access_account_misc_import_result_wizard,access_account_misc_import_result_wizard,model_account_misc_import_result_wizard,account.group_account_user,1,1,1,1 +access_account_misc_import_result_line,access_account_misc_import_result_line,model_account_misc_import_result_line,account.group_account_user,1,1,1,1 diff --git a/account_journal_import/views/account_move_view.xml b/account_journal_import/views/account_move_view.xml new file mode 100644 index 0000000..cc73d1b --- /dev/null +++ b/account_journal_import/views/account_move_view.xml @@ -0,0 +1,26 @@ + + + + Import Journal Items (CSV/XLSX) + account.misc.import.wizard + form + new + {} + + + + + + + + + \ No newline at end of file diff --git a/account_journal_import/views/misc_import_wizard_view.xml b/account_journal_import/views/misc_import_wizard_view.xml new file mode 100644 index 0000000..07d9016 --- /dev/null +++ b/account_journal_import/views/misc_import_wizard_view.xml @@ -0,0 +1,76 @@ + + + + + + account.misc.import.wizard.form + account.misc.import.wizard + + +
+ + + + + + + + +
+
+ +
+
+ + + + account.misc.import.result.wizard.form + account.misc.import.result.wizard + +
+ + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ +