diff --git a/erpnext/accounts/doctype/accounting_journal/accounting_journal.json b/erpnext/accounts/doctype/accounting_journal/accounting_journal.json
index 5eb50288e3acc06d4ff619413b385fc8b0232793..3cef00d12ea8535677f26db7e5cb5e1b831e9766 100644
--- a/erpnext/accounts/doctype/accounting_journal/accounting_journal.json
+++ b/erpnext/accounts/doctype/accounting_journal/accounting_journal.json
@@ -23,8 +23,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Journal Name",
- "reqd": 1,
- "unique": 1
+ "reqd": 1
},
{
"fieldname": "type",
@@ -79,10 +78,11 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-03-15 09:29:59.117824",
+ "modified": "2023-06-14 12:35:23.944466",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Journal",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -119,6 +119,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "journal_name",
"track_changes": 1
}
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 47a8becb08764a69de2be5ab78a70acc9d5c502d..a17b53e4a4f84c7ff073afa05c38d89fee981767 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -102,7 +102,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "ACC-JV-.YYYY.-",
+ "options": "ACC-JV-.FY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -570,7 +570,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
- "modified": "2023-08-10 14:32:22.366895",
+ "modified": "2023-08-24 14:41:28.097349",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ff82488ce1453ed5c782868af02b3f04e9b890c4..bf2a7fb1aeb4bcb67ed6787139d20e25d16eeaef 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -35,6 +35,80 @@ class StockAccountInvalidTransaction(frappe.ValidationError):
class JournalEntry(AccountsController):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import (
+ JournalEntryAccount,
+ )
+
+ accounting_journal: DF.Link | None
+ accounts: DF.Table[JournalEntryAccount]
+ amended_from: DF.Link | None
+ apply_tds: DF.Check
+ auto_repeat: DF.Link | None
+ bill_date: DF.Date | None
+ bill_no: DF.Data | None
+ cheque_date: DF.Date | None
+ cheque_no: DF.Data | None
+ clearance_date: DF.Date | None
+ company: DF.Link
+ difference: DF.Currency
+ due_date: DF.Date | None
+ finance_book: DF.Link | None
+ from_template: DF.Link | None
+ inter_company_journal_entry_reference: DF.Link | None
+ is_opening: DF.Literal["No", "Yes"]
+ is_system_generated: DF.Check
+ letter_head: DF.Link | None
+ mode_of_payment: DF.Link | None
+ multi_currency: DF.Check
+ naming_series: DF.Literal["ACC-JV-.FY.-"]
+ paid_loan: DF.Data | None
+ pay_to_recd_from: DF.Data | None
+ payment_order: DF.Link | None
+ posting_date: DF.Date
+ process_deferred_accounting: DF.Link | None
+ remark: DF.SmallText | None
+ reversal_of: DF.Link | None
+ select_print_heading: DF.Link | None
+ stock_entry: DF.Link | None
+ tax_withholding_category: DF.Link | None
+ title: DF.Data | None
+ total_amount: DF.Currency
+ total_amount_currency: DF.Link | None
+ total_amount_in_words: DF.Data | None
+ total_credit: DF.Currency
+ total_debit: DF.Currency
+ unreconciled_amount: DF.Currency
+ user_remark: DF.SmallText | None
+ voucher_type: DF.Literal[
+ "Journal Entry",
+ "Inter Company Journal Entry",
+ "Bank Entry",
+ "Cash Entry",
+ "Credit Card Entry",
+ "Debit Note",
+ "Credit Note",
+ "Contra Entry",
+ "Excise Entry",
+ "Write Off Entry",
+ "Opening Entry",
+ "Depreciation Entry",
+ "Exchange Rate Revaluation",
+ "Exchange Gain Or Loss",
+ "Deferred Revenue",
+ "Deferred Expense",
+ ]
+ write_off_amount: DF.Currency
+ write_off_based_on: DF.Literal["Accounts Receivable", "Accounts Payable"]
+ # end: auto-generated types
+
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
@@ -485,13 +559,13 @@ class JournalEntry(AccountsController):
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(
_(
- "Row #{0}: For {1}, you can select reference document only if account gets credited"
+ "Row #{0}: For {1}, you can select a reference document only if account gets credited"
).format(d.idx, d.account)
)
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(
_(
- "Row #{0}: For {1}, you can select reference document only if account gets debited"
+ "Row #{0}: For {1}, you can select a reference document only if account gets debited"
).format(d.idx, d.account)
)
@@ -602,9 +676,9 @@ class JournalEntry(AccountsController):
frappe.throw(
_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}").format(
d.idx,
- field_dict.get(d.reference_type)[0],
- field_dict.get(d.reference_type)[1],
- d.reference_type,
+ _(field_dict.get(d.reference_type)[0]),
+ _(field_dict.get(d.reference_type)[1]),
+ _(d.reference_type),
d.reference_name,
)
)
@@ -932,6 +1006,7 @@ class JournalEntry(AccountsController):
"project": d.project,
"finance_book": self.finance_book,
"accounting_journal": d.accounting_journal,
+ "accounting_entry_number": self.flags.accounting_entry_number,
},
item=d,
)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 2e9111099dbf69be9a826ce7b16dc2380afbc19a..5517a21723be9fb342b2cc2492040a61fa710753 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -370,7 +370,9 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not entry.get("accounting_journal"):
get_accounting_journal(entry)
- entry["accounting_entry_number"] = accounting_number
+ if not entry.get("accounting_entry_number"):
+ entry["accounting_entry_number"] = accounting_number
+
make_entry(entry, adv_adj, update_outstanding, from_repost)
diff --git a/erpnext/regional/doctype/fec_accounting_journal_mapping/__init__.py b/erpnext/regional/doctype/fec_accounting_journal_mapping/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.json b/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.json
new file mode 100644
index 0000000000000000000000000000000000000000..8209f96f62057a51a6ea8bfb1f588c38039a7960
--- /dev/null
+++ b/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.json
@@ -0,0 +1,41 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-06-08 18:04:30.601238",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "accounting_journal_in_fec",
+ "accounting_journal_in_dokos"
+ ],
+ "fields": [
+ {
+ "fieldname": "accounting_journal_in_fec",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Accounting Journal in FEC",
+ "reqd": 1
+ },
+ {
+ "fieldname": "accounting_journal_in_dokos",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Accounting Journal in Dokos",
+ "options": "Accounting Journal",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-06-08 18:04:30.601238",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "FEC Accounting Journal Mapping",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.py b/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.py
new file mode 100644
index 0000000000000000000000000000000000000000..149bae29617b1efdf0a0213288a0d9e6d90f3301
--- /dev/null
+++ b/erpnext/regional/doctype/fec_accounting_journal_mapping/fec_accounting_journal_mapping.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Dokos SAS and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class FECAccountingJournalMapping(Document):
+ pass
diff --git a/erpnext/regional/doctype/fec_import/__init__.py b/erpnext/regional/doctype/fec_import/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/erpnext/regional/doctype/fec_import/fec_import.js b/erpnext/regional/doctype/fec_import/fec_import.js
new file mode 100644
index 0000000000000000000000000000000000000000..4365669585d0e085bab3e6e126a776750507f36e
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import/fec_import.js
@@ -0,0 +1,144 @@
+// Copyright (c) 2023, Dokos SAS and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('FEC Import', {
+ setup(frm) {
+ frm.set_query("import_settings", function(doc) {
+ return {
+ filters: {
+ "company": doc.company
+ }
+ }
+ })
+
+ frappe.realtime.on('fec_doc_update', data => {
+ if (data.fec_import !== frm.doc.name) return;
+ const message = __('{0} of {1} {2}', [data.current, data.total, data.type]);
+ const percent = Math.floor((data.current * 100) / data.total);
+ const title = __("{0} creation", [data.type])
+ frm.dashboard.show_progress(title, percent, message);
+ })
+ },
+ refresh(frm) {
+ frm.get_field("fec_file").df.options = {
+ restrictions: {
+ allowed_file_types: [".txt", ".csv"],
+ },
+ };
+
+ frm.trigger("add_description");
+ frm.trigger("add_actions");
+ },
+
+ add_actions(frm) {
+ if (frm.doc.fec_file) {
+ frm.page.set_primary_action(__("Import FEC"), function() {
+ frappe.show_alert({
+ message: __("Import started"),
+ indicator: "orange"
+ })
+ frappe.call({
+ method: "upload_fec",
+ doc: frm.doc,
+ }).then(r => {
+ frappe.show_alert({
+ message: __("Import finished. Please check the imported documents."),
+ indicator: "green"
+ })
+ })
+ });
+
+ frm.add_custom_button(__("Create Accounts"), function() {
+ frappe.show_alert({
+ message: __("Accounts creation started"),
+ indicator: "orange"
+ })
+ frappe.call({
+ method: "create_accounts",
+ doc: frm.doc,
+ }).then(r => {
+ frappe.show_alert({
+ message: __("Accounts created.
Please setup a correct account type for each."),
+ indicator: "green"
+ })
+ })
+ }, __("Actions"));
+
+ frm.add_custom_button(__("Create Journals"), function() {
+ frappe.show_alert({
+ message: __("Journals creation started"),
+ indicator: "orange"
+ })
+ frappe.call({
+ method: "create_journals",
+ doc: frm.doc,
+ }).then(r => {
+ frappe.show_alert({
+ message: __("Accounting journals created.
Please setup a correct journal type for each."),
+ indicator: "green"
+ })
+ })
+ }, __("Actions"));
+
+ frm.add_custom_button(__("View Accounts"), function() {
+ frappe.set_route("Tree", "Account")
+ }, __("View"));
+
+ frm.add_custom_button(__("View Journals"), function() {
+ frappe.set_route("List", "Accounting Journal")
+ }, __("View"));
+ }
+
+ frappe.model.with_doctype("FEC Import Document", () => {
+ frappe.db.get_list("FEC Import Document", {
+ filters: {
+ fec_import: frm.doc.name,
+ }
+ }).then(r => {
+ frm.add_custom_button(__("Auto reconcile open entries"), function() {
+ frappe.show_alert({
+ message: __("Reconciliation started"),
+ indicator: "orange"
+ })
+ frappe.call({
+ method: "auto_reconcile_entries",
+ doc: frm.doc,
+ }).then(r => {
+ frappe.show_alert({
+ message: __("Reconciliation completed."),
+ indicator: "green"
+ })
+ })
+ }, __("Actions"));
+ })
+ })
+ },
+
+ add_description(frm) {
+ let description = __("1. Import your FEC file")
+ if (!frm.is_new()) {
+ description += ""
+ description += __("2. Import your accounts.")
+ description += ""
+ description += ""
+ description += __("This operation will not erase your existing accounts. Dokos will try to append them to their corresponding parents in the tree.")
+ description += ""
+ description += ""
+ description += __("3. Configure your accounts properly by adding the correct account type, especially for receivable and payable accounts (Classe 4).")
+ description += ""
+ description += __("4. Import your accounting journals.")
+ description += ""
+ description += ""
+ description += __("This operation will not erase your existing accounting journals.")
+ description += ""
+
+ description += ""
+ description += __("5. Configure your journals properly, especially the Bank and Cash journals.")
+ }
+
+ description += ""
+
+ frm.get_field("description").$wrapper.html(description)
+ frm.get_field("description").$wrapper.addClass("mb-4")
+ }
+});
diff --git a/erpnext/regional/doctype/fec_import/fec_import.json b/erpnext/regional/doctype/fec_import/fec_import.json
new file mode 100644
index 0000000000000000000000000000000000000000..3341281c2051ec186d674612fd966d3ffb60515d
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import/fec_import.json
@@ -0,0 +1,148 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "naming_series:",
+ "beta": 1,
+ "creation": "2023-06-08 16:51:07.044126",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "description",
+ "fec_file",
+ "section_break_3",
+ "company",
+ "column_break_lduy",
+ "import_settings",
+ "filters_section",
+ "from_date",
+ "column_break_7",
+ "to_date",
+ "section_break_9",
+ "import_journal"
+ ],
+ "fields": [
+ {
+ "default": "FEC-.YYYY.-.#####",
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Naming Series",
+ "options": "FEC-.YYYY.-.#####",
+ "reqd": 1
+ },
+ {
+ "fieldname": "fec_file",
+ "fieldtype": "Attach",
+ "in_list_view": 1,
+ "label": "FEC File",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "eval:doc.fec_file",
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company"
+ },
+ {
+ "depends_on": "eval:doc.fec_file && doc.company",
+ "fieldname": "filters_section",
+ "fieldtype": "Section Break",
+ "hide_border": 1,
+ "label": "Filters"
+ },
+ {
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "label": "Import From Date"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "label": "Import To Date"
+ },
+ {
+ "depends_on": "eval:doc.fec_file && doc.company",
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "import_journal",
+ "fieldtype": "Link",
+ "label": "Import From Journal",
+ "options": "Accounting Journal"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "HTML",
+ "label": "Description"
+ },
+ {
+ "fieldname": "column_break_lduy",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "import_settings",
+ "fieldtype": "Link",
+ "label": "Import Setttings",
+ "options": "FEC Import Settings"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [
+ {
+ "group": "Import Details",
+ "link_doctype": "FEC Import Document",
+ "link_fieldname": "fec_import"
+ }
+ ],
+ "modified": "2023-08-28 14:27:25.146981",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "FEC Import",
+ "naming_rule": "By \"Naming Series\" field",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/erpnext/regional/doctype/fec_import/fec_import.py b/erpnext/regional/doctype/fec_import/fec_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..020bf90b673b1eed0a97842cec6f88acb0420085
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import/fec_import.py
@@ -0,0 +1,476 @@
+# Copyright (c) 2023, Dokos SAS and contributors
+# For license information, please see license.txt
+
+import codecs
+import datetime
+import hashlib
+from collections import defaultdict
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import cint, flt, get_year_start, getdate
+from frappe.utils.csvutils import read_csv_content
+
+
+class FECImport(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ company: DF.Link | None
+ fec_file: DF.Attach | None
+ from_date: DF.Date | None
+ import_journal: DF.Link | None
+ import_settings: DF.Link | None
+ naming_series: DF.Literal["FEC-.YYYY.-.#####"]
+ to_date: DF.Date | None
+ # end: auto-generated types
+
+ def validate(self):
+ company_and_dates = self.get_company_and_dates()
+ if not self.company:
+ self.company = company_and_dates.company
+
+ if not self.from_date:
+ self.from_date = company_and_dates.from_date
+
+ if not self.to_date:
+ self.to_date = company_and_dates.to_date
+
+ if self.company and not self.import_settings:
+ if (
+ settings := frappe.get_all(
+ "FEC Import Settings", filters={"company": self.company}, pluck="name"
+ )
+ ) and len(settings) == 1:
+ self.import_settings = settings[0]
+
+ @frappe.whitelist()
+ def get_company_and_dates(self):
+ company_and_dates = frappe._dict(company=None, from_date=None, to_date=None)
+
+ if self.fec_file:
+ file_name = frappe.db.get_value(
+ "File",
+ {
+ "file_url": self.fec_file,
+ "attached_to_doctype": self.doctype,
+ "attached_to_name": self.name,
+ },
+ "file_name",
+ )
+ try:
+ if closing_date := file_name.split("FEC")[1]:
+ closing_date = closing_date.split(".txt")[0]
+ company_and_dates["to_date"] = datetime.datetime.strptime(closing_date, "%Y%m%d").strftime(
+ "%Y-%m-%d"
+ )
+ company_and_dates["from_date"] = get_year_start(closing_date, as_str=True)
+
+ if siren := file_name.split("FEC")[0]:
+ company_and_dates["company"] = frappe.db.get_value("Company", dict(siren_number=siren))
+ except Exception:
+ company_and_dates
+
+ return company_and_dates
+
+ @frappe.whitelist()
+ def upload_fec(self):
+ if not self.company:
+ frappe.throw(_("Please select a company"))
+
+ if not self.import_settings:
+ frappe.throw(_("Please select an import settings document"))
+
+ data = self.get_data()
+
+ try:
+ import_in_progress = FECImportDocumentCreator(settings=self, data=data)
+ import_in_progress.import_data()
+ except Exception:
+ print(frappe.get_traceback())
+
+ def get_data(self):
+ fileid = frappe.db.get_value(
+ "File",
+ {"file_url": self.fec_file, "attached_to_doctype": self.doctype, "attached_to_name": self.name},
+ )
+
+ _file = frappe.get_doc("File", fileid)
+ fcontent = _file.get_content()
+ rows = read_csv_content(fcontent, delimiter="\t")
+
+ header = rows[0]
+ data = rows[1:]
+
+ output = list()
+ for d in data:
+ row = frappe._dict()
+ for count, head in enumerate(header):
+ if head:
+ if head.__contains__(codecs.BOM_UTF8.decode("utf-8")):
+ # A Byte Order Mark is present
+ head = head.strip(codecs.BOM_UTF8.decode("utf-8"))
+ row[head] = d[count] or ""
+
+ output.append(row)
+
+ return output
+
+ @frappe.whitelist()
+ def create_journals(self):
+ journals = {l["JournalCode"]: l["JournalLib"] for l in self.get_data()}
+ bank_journals = list(
+ {l["JournalCode"] for l in self.get_data() if l["CompteNum"].startswith("512")}
+ )
+ cash_journals = list(
+ {l["JournalCode"] for l in self.get_data() if l["CompteNum"].startswith("53")}
+ )
+ sales_journals = list(
+ {l["JournalCode"] for l in self.get_data() if l["CompteNum"].startswith("7")}
+ )
+ purchase_journals = list(
+ {l["JournalCode"] for l in self.get_data() if l["CompteNum"].startswith("6")}
+ )
+
+ for index, journal in enumerate(journals):
+ frappe.publish_realtime(
+ "fec_doc_update",
+ {
+ "fec_import": self.name,
+ "current": index + 1,
+ "total": len(journals),
+ "type": _("accounting journals created"),
+ },
+ )
+ journal_type = "Miscellaneous"
+ account = None
+
+ if journal in bank_journals:
+ journal_type = "Bank"
+ account_number = list(
+ {
+ l["CompteNum"]
+ for l in self.get_data()
+ if l["JournalCode"] == journal and l["CompteNum"].startswith("512")
+ }
+ )[0]
+ account = frappe.get_value("Account", dict(account_number=account_number))
+ elif journal in cash_journals:
+ journal_type = "Cash"
+ account_number = list(
+ {
+ l["CompteNum"]
+ for l in self.get_data()
+ if l["JournalCode"] == journal and l["CompteNum"].startswith("53")
+ }
+ )[0]
+ account = frappe.get_value("Account", dict(account_number=account_number))
+ elif journal in sales_journals:
+ journal_type = "Sales"
+ elif journal in purchase_journals:
+ journal_type = "Purchase"
+
+ doc = frappe.new_doc("Accounting Journal")
+ doc.company = self.company
+ doc.journal_code = journal
+ doc.journal_name = journals[journal]
+ doc.type = journal_type
+ doc.account = account
+ doc.insert(ignore_if_duplicate=True)
+
+ @frappe.whitelist()
+ def create_accounts(self):
+ accounts = {l["CompteNum"]: l["CompteLib"] for l in self.get_data()}
+ account_groups = frappe.get_all(
+ "Account",
+ filters={
+ "disabled": 0,
+ "is_group": 1,
+ "account_number": ("is", "set"),
+ "company": self.company,
+ "do_not_show_account_number": 0,
+ },
+ fields=["name", "parent_account", "account_number"],
+ )
+ accounts_with_number_list = [x.name for x in account_groups]
+
+ account_groups_with_numbers = {}
+ for group in account_groups:
+ if not group.parent_account in accounts_with_number_list:
+ account_groups_with_numbers.update({group.account_number[:-1]: group.parent_account})
+ account_groups_with_numbers.update({group.account_number: group.name})
+
+ for index, account in enumerate(accounts):
+ frappe.publish_realtime(
+ "fec_doc_update",
+ {
+ "fec_import": self.name,
+ "current": index + 1,
+ "total": len(accounts),
+ "type": _("accounts created"),
+ },
+ )
+ if not frappe.db.exists("Account", dict(account_number=account)):
+ doc = frappe.new_doc("Account")
+ doc.company = self.company
+ doc.account_name = accounts[account]
+ doc.account_number = account
+ doc.parent_account = self.get_parent_account(account_groups_with_numbers, account)
+ doc.account_type = self.get_account_type(account)
+ doc.insert(ignore_if_duplicate=True)
+
+ def get_parent_account(self, account_groups, account):
+ if frappe.db.exists(
+ "Account", dict(account_number=str(account)[:-1], disabled=0, company=self.company)
+ ) and account_groups.get(account[:-1]):
+ return account_groups.get(account[:-1])
+
+ account_numbers = [key for key, value in account_groups.items()]
+ for idx, acc in enumerate(account):
+ sub_number = account[:idx]
+ if sub_number in account_numbers:
+ continue
+ elif account_groups.get(account[: idx - 1]):
+ return account_groups[account[: idx - 1]]
+
+ root_mapping = {
+ "1": "Equity",
+ "2": "Asset",
+ "3": "Asset",
+ "4": "Liability",
+ "5": "Equity",
+ "6": "Expense",
+ "7": "Income",
+ }
+
+ return frappe.db.get_value(
+ "Account",
+ dict(
+ disabled=0,
+ root_type=root_mapping.get(str(account)[0]),
+ company=self.company,
+ parent_account=("is", "not set"),
+ ),
+ )
+
+ def get_account_type(self, account_number):
+ if cint(account_number[:3]) in range(400, 409):
+ return "Payable"
+
+ elif cint(account_number[:3]) in range(410, 419):
+ return "Receivable"
+
+ elif cint(account_number[:3]) == 512:
+ return "Bank"
+
+ elif cint(account_number[:2]) == 53:
+ return "Cash"
+
+ elif cint(account_number[:2]) == 10:
+ return "Equity"
+
+ @frappe.whitelist()
+ def auto_reconcile_entries(self):
+ from erpnext.accounts.report.accounts_receivable.accounts_receivable import (
+ ReceivablePayableReport,
+ )
+
+ for party_type in ["Customer", "Supplier"]:
+ args = {
+ "account_type": "Payable" if party_type == "Supplier" else "Receivable",
+ "naming_by": ["Buying Settings", "supp_master_name"]
+ if party_type == "Supplier"
+ else ["Selling Settings", "cust_master_name"],
+ }
+
+ filters = {
+ "company": self.company,
+ "ageing_based_on": "Due Date",
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
+ }
+
+ result = ReceivablePayableReport(filters).run(args)
+ parties = set(
+ (d.get("party"), d.get("party_account")) for d in result[1] if d.get("party") != "Total"
+ )
+
+ for index, data in enumerate(parties):
+ frappe.publish_realtime(
+ "fec_doc_update",
+ {
+ "fec_import": self.name,
+ "current": index + 1,
+ "total": len(parties),
+ "type": _("parties reconciled"),
+ },
+ )
+ pr = self.create_payment_reconciliation(party_type, data[0], data[1])
+ pr.get_unreconciled_entries()
+ invoices = [x.as_dict() for x in pr.get("invoices")]
+ payments = [x.as_dict() for x in pr.get("payments")]
+ if invoices and payments:
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+
+ def create_payment_reconciliation(self, party_type, party, party_account):
+ pr = frappe.new_doc("Payment Reconciliation")
+ pr.company = self.company
+ pr.party_type = party_type
+ pr.party = party
+ pr.receivable_payable_account = party_account
+ return pr
+
+
+class FECImportDocumentCreator:
+ def __init__(self, settings, data):
+ self.settings = settings
+ if not self.settings.import_settings:
+ frappe.throw(
+ _("Please configure a FEC Import Settings document for company {0}").format(
+ self.settings.company
+ )
+ )
+
+ self.data = data
+
+ def import_data(self):
+ self.group_data()
+ self.create_fec_import_documents()
+ self.process_fec_import_documents()
+
+ def group_data(self):
+ self.grouped_data = defaultdict(lambda: defaultdict(list))
+ accounting_journals = self.get_accounting_journals_mapping()
+
+ for index, d in enumerate(self.data):
+ frappe.publish_realtime(
+ "fec_doc_update",
+ {
+ "fec_import": self.settings.name,
+ "current": index + 1,
+ "total": len(self.data),
+ "type": _("line processed"),
+ },
+ )
+ if not self.is_within_date_range(d):
+ continue
+
+ if (
+ self.settings.import_journal
+ and not accounting_journals.get(d.get("JournalCode")) == self.settings.import_journal
+ ):
+ continue
+
+ self.grouped_data[d["EcritureDate"]][
+ f'{d["JournalCode"]} | {d["EcritureNum"] or d["PieceRef"]}'
+ ].append(frappe._dict(d))
+
+ @staticmethod
+ def parse_credit_debit(d):
+ d["Debit"] = flt(d["Debit"].replace(",", "."))
+ d["Credit"] = flt(d["Credit"].replace(",", "."))
+ d["Montantdevise"] = flt(d["Montantdevise"].replace(",", "."))
+
+ def create_fec_import_documents(self):
+ current_index = 0
+ total_elements = sum(len(self.grouped_data[date]) for date in self.grouped_data)
+ for date in self.grouped_data:
+ for piece in self.grouped_data[date]:
+ current_index += 1
+ frappe.publish_realtime(
+ "fec_doc_update",
+ {
+ "fec_import": self.settings.name,
+ "current": current_index,
+ "total": total_elements,
+ "type": _("voucher creation initiated"),
+ },
+ )
+ iter_next = False
+ doc = frappe.new_doc("FEC Import Document")
+ doc.fec_import = self.settings.name
+ doc.settings = self.settings.import_settings
+ doc.gl_entries_date = datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d")
+ doc.gl_entry_reference = piece
+
+ for line in self.grouped_data[date][piece]:
+ concatenated_data = "".join([value for key, value in line.items()])
+ self.parse_credit_debit(line)
+ row = {frappe.scrub(key): value for key, value in line.items()}
+ row["hashed_data"] = hash_line(concatenated_data)
+
+ if frappe.db.exists("FEC Import Line", dict(hashed_data=row["hashed_data"])):
+ iter_next = True
+ break
+
+ doc.append("gl_entries", row)
+
+ if iter_next:
+ continue
+
+ doc.insert()
+
+ def process_fec_import_documents(self):
+ groups = {"Transaction": [], "Payment": [], "Miscellaneous": []}
+
+ for doc in frappe.get_all(
+ "FEC Import Document",
+ filters={"status": "Pending"},
+ fields=["name", "import_type"],
+ order_by="gl_entries_date",
+ ):
+ groups[doc.import_type].append(doc.name)
+
+ for group in ["Transaction", "Miscellaneous", "Payment"]:
+ for d in groups[group]:
+ frappe.get_doc("FEC Import Document", d).run_method("process_document_in_background")
+
+ def is_within_date_range(self, line):
+ posting_date = datetime.datetime.strptime(line.EcritureDate, "%Y%m%d").strftime("%Y-%m-%d")
+
+ if self.settings.from_date and getdate(posting_date) < getdate(self.settings.from_date):
+ return False
+
+ if self.settings.to_date and getdate(posting_date) > getdate(self.settings.to_date):
+ return False
+
+ return True
+
+ def get_accounting_journals_mapping(self):
+ dokos_journals = {
+ x.journal_code: x.name
+ for x in frappe.get_all(
+ "Accounting Journal", filters={"disabled": 0}, fields=["journal_code", "name"]
+ )
+ }
+
+ company_settings = frappe.get_doc("FEC Import Settings", self.settings.import_settings)
+
+ journals = {}
+ mapped_journals = dokos_journals
+ for mapping in company_settings.get("accounting_journal_mapping", []):
+ for j in mapped_journals:
+ if j == mapping.accounting_journal_in_dokos:
+ journals[mapping.accounting_journal_in_fec] = mapped_journals[j]
+ continue
+
+ journals[j] = mapped_journals[j]
+
+ mapped_journals = journals
+
+ return journals or dokos_journals
+
+
+def hash_line(data):
+ sha = hashlib.sha256()
+ sha.update(frappe.safe_encode(str(data)))
+ return sha.hexdigest()
diff --git a/erpnext/regional/doctype/fec_import/test_fec_import.py b/erpnext/regional/doctype/fec_import/test_fec_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..453ac4a29d1f96f145a63dfb4b9afcfbb371d393
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import/test_fec_import.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Dokos SAS and Contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestFECImport(FrappeTestCase):
+ pass
diff --git a/erpnext/regional/doctype/fec_import_document/__init__.py b/erpnext/regional/doctype/fec_import_document/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document.js b/erpnext/regional/doctype/fec_import_document/fec_import_document.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c506c165363a4e7904677620eac0d973d0efeb2
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_document/fec_import_document.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2023, Dokos SAS and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('FEC Import Document', {
+ refresh: function(frm) {
+ if (frm.doc.status != "Completed") {
+ frm.add_custom_button(`${__("Retry an integration")}`, () => {
+ frappe.call({
+ method: "create_linked_document",
+ doc: frm.doc
+ }).then(r => {
+ frappe.show_alert({
+ message: __("Retry in progress"),
+ indicator: "orange"
+ })
+
+ frm.refresh()
+ })
+ });
+ }
+ }
+});
diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document.json b/erpnext/regional/doctype/fec_import_document/fec_import_document.json
new file mode 100644
index 0000000000000000000000000000000000000000..79aada1c5f012833042b888052a4f3ebb239bf99
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_document/fec_import_document.json
@@ -0,0 +1,188 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-06-14 09:33:57.672790",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "fec_import",
+ "status",
+ "import_type",
+ "error",
+ "column_break_4",
+ "settings",
+ "company",
+ "data_section",
+ "gl_entries_date",
+ "column_break_8",
+ "gl_entry_reference",
+ "pieceref",
+ "section_break_10",
+ "gl_entries",
+ "section_break_13",
+ "linked_document_type",
+ "column_break_15",
+ "linked_document"
+ ],
+ "fields": [
+ {
+ "fieldname": "fec_import",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "FEC Import",
+ "options": "FEC Import",
+ "reqd": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "Pending\nCompleted\nError",
+ "reqd": 1
+ },
+ {
+ "depends_on": "eval:doc.status==\"Error\"",
+ "fieldname": "error",
+ "fieldtype": "Small Text",
+ "label": "Error",
+ "read_only": 1
+ },
+ {
+ "fieldname": "data_section",
+ "fieldtype": "Section Break",
+ "hide_border": 1,
+ "label": "FEC Data"
+ },
+ {
+ "fieldname": "gl_entries",
+ "fieldtype": "Table",
+ "label": "GL Entries",
+ "options": "FEC Import Line"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "settings",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Settings",
+ "options": "FEC Import Settings",
+ "reqd": 1
+ },
+ {
+ "fieldname": "gl_entries_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "GL Entries Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "gl_entry_reference",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "GL Entry Reference",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fetch_from": "settings.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_13",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "linked_document_type",
+ "fieldtype": "Link",
+ "label": "Linked Document Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "column_break_15",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "linked_document",
+ "fieldtype": "Dynamic Link",
+ "label": "Linked Document",
+ "options": "linked_document_type"
+ },
+ {
+ "default": "Miscellaneous",
+ "fieldname": "import_type",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Import Type",
+ "options": "Miscellaneous\nTransaction\nPayment",
+ "reqd": 1
+ },
+ {
+ "fieldname": "pieceref",
+ "fieldtype": "Data",
+ "label": "PieceRef",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-08-24 14:42:15.131156",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "FEC Import Document",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "gl_entry_reference"
+}
diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document.py b/erpnext/regional/doctype/fec_import_document/fec_import_document.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9db1f90d8ce0713cb072738aa9b05fc910d57e3
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_document/fec_import_document.py
@@ -0,0 +1,534 @@
+# Copyright (c) 2023, Dokos SAS and contributors
+# For license information, please see license.txt
+
+import datetime
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import flt, get_year_ending, get_year_start, getdate
+from tenacity import retry, retry_if_result, stop_after_attempt
+
+from erpnext.accounts.utils import FiscalYearError, get_fiscal_years
+
+
+def value_is_true(value):
+ return value is True
+
+
+class FECImportDocument(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ from erpnext.regional.doctype.fec_import_line.fec_import_line import FECImportLine
+
+ company: DF.Link
+ error: DF.SmallText | None
+ fec_import: DF.Link
+ gl_entries: DF.Table[FECImportLine]
+ gl_entries_date: DF.Date | None
+ gl_entry_reference: DF.Data | None
+ import_type: DF.Literal["Miscellaneous", "Transaction", "Payment"]
+ linked_document: DF.DynamicLink | None
+ linked_document_type: DF.Link | None
+ pieceref: DF.Data | None
+ settings: DF.Link
+ status: DF.Literal["Pending", "Completed", "Error"]
+ # end: auto-generated types
+
+ def validate(self):
+ if self.status != "Completed":
+ for row in self.gl_entries:
+ self.set_pieceref()
+ self.get_accounting_journal(row)
+ self.get_gl_account(row)
+ self.get_party(row)
+ self.parse_dates(row)
+
+ def before_insert(self):
+ self.set_import_type()
+
+ def set_import_type(self):
+ if self.is_payment_entry():
+ self.import_type = "Payment"
+ elif [l.party for l in self.gl_entries if l.party]:
+ self.import_type = "Transaction"
+ else:
+ self.import_type = "Miscellaneous"
+
+ def check_fiscal_year(self):
+ try:
+ posting_date = getdate(self.gl_entries_date)
+ get_fiscal_years(posting_date)
+ except FiscalYearError:
+ # TODO: Create Fiscal Year based on FEC file name
+ frappe.clear_messages()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Fiscal Year",
+ "year": posting_date.year,
+ "year_start_date": get_year_start(posting_date),
+ "year_end_date": get_year_ending(posting_date),
+ }
+ )
+ doc.insert(ignore_if_duplicate=True)
+
+ def set_pieceref(self):
+ references = set(line.pieceref for line in self.gl_entries if line.pieceref)
+ if len(references) == 1:
+ self.pieceref = list(references)[0]
+
+ def get_accounting_journal(self, row):
+ if row.accounting_journal:
+ return
+
+ journals = self.get_accounting_journals_mapping()
+ row.accounting_journal = journals.get(row.journalcode)
+
+ def get_accounting_journals_mapping(self):
+ dokos_journals = {
+ x.journal_code: x.name
+ for x in frappe.get_all(
+ "Accounting Journal", filters={"disabled": 0}, fields=["journal_code", "name"]
+ )
+ }
+
+ company_settings = frappe.get_doc("FEC Import Settings", self.settings)
+
+ journals = {}
+ mapped_journals = dokos_journals
+ for mapping in company_settings.get("accounting_journal_mapping", []):
+ for j in mapped_journals:
+ if j == mapping.accounting_journal_in_dokos:
+ journals[mapping.accounting_journal_in_fec] = mapped_journals[j]
+ continue
+
+ journals[j] = mapped_journals[j]
+
+ mapped_journals = journals
+
+ return journals or dokos_journals
+
+ def get_gl_account(self, row):
+ if row.account:
+ return
+
+ accounts = {
+ x.account_number.strip(): x.name
+ for x in frappe.get_all(
+ "Account",
+ filters={"disabled": 0, "account_number": ("is", "set")},
+ fields=["name", "account_number"],
+ )
+ }
+
+ row.account = accounts.get(row.comptenum)
+
+ def get_party(self, row):
+ if not row.compauxnum or (row.party_type and row.party):
+ return
+
+ journal_type = frappe.get_cached_value("Accounting Journal", row.accounting_journal, "type")
+ account_type = frappe.get_cached_value("Account", row.account, "account_type")
+
+ compte_aux = None
+ compte_aux_type = None
+ if journal_type in ("Sales", "Bank") or account_type == "Receivable":
+ if compte_aux := frappe.db.exists("Customer", row.compauxnum):
+ compte_aux_type = "Customer"
+ elif journal_type in ("Purchase", "Bank") or account_type == "Payable":
+ if compte_aux := frappe.db.exists("Supplier", row.compauxnum):
+ compte_aux_type = "Supplier"
+
+ if account_type in ["Receivable", "Payable"] and not (compte_aux and compte_aux_type):
+ if account_type == "Receivable":
+ compte_aux_type = "Customer"
+ compte_aux = self.create_customer(compte_aux, row.compauxlib)
+ else:
+ compte_aux_type = "Supplier"
+ compte_aux = self.create_supplier(compte_aux, row.compauxlib)
+
+ row.party_type = compte_aux_type
+ row.party = compte_aux
+
+ def create_customer(self, compauxnum, compauxlib):
+ company_settings = frappe.get_doc("FEC Import Settings", self.settings)
+
+ customer = frappe.new_doc("Customer")
+ customer.name = compauxnum
+ customer.customer_name = compauxlib
+ customer.customer_group = company_settings.customer_group or frappe.db.get_single_value(
+ "Selling Settings", "customer_group"
+ )
+ customer.territory = company_settings.territory or frappe.db.get_single_value(
+ "Selling Settings", "territory"
+ )
+
+ frappe.flags.in_import = True
+ customer.flags.name_set = True
+ customer.insert(ignore_if_duplicate=True)
+ frappe.flags.in_import = False
+
+ return customer.name
+
+ def create_supplier(self, compauxnum, compauxlib):
+ company_settings = frappe.get_doc("FEC Import Settings", self.settings)
+
+ supplier = frappe.new_doc("Supplier")
+ supplier.name = compauxnum
+ supplier.supplier_name = compauxlib
+ supplier.supplier_group = company_settings.supplier_group or frappe.db.get_single_value(
+ "Buying Settings", "supplier_group"
+ )
+
+ frappe.flags.in_import = True
+ supplier.flags.name_set = True
+ supplier.insert(ignore_if_duplicate=True)
+ frappe.flags.in_import = False
+
+ return supplier.name
+
+ def parse_dates(self, row):
+ row.posting_date = parse_date(row.ecrituredate)
+ row.transaction_date = parse_date(row.piecedate)
+ row.validation_date = parse_date(row.validdate)
+ row.reconciliation_date = parse_date(row.datelet)
+
+ @frappe.whitelist()
+ def create_linked_document(self):
+ self.run_method("validate")
+
+ fields = ["accounting_journal", "account"]
+ for line in self.gl_entries:
+ for field in fields:
+ if not line.get(field):
+ frappe.throw(
+ _(
+ "Row #{0}: Data for field {1} could not be found automatically. Please select it manually"
+ ).format(line.idx, frappe.unscrub(field))
+ )
+
+ return self.create_references()
+
+ def process_document_in_background(self, defer_payments=False):
+ frappe.enqueue_doc(
+ self.doctype, self.name, "create_references", defer_payments=defer_payments, timeout=1000
+ )
+
+ @retry(stop=stop_after_attempt(5), retry=retry_if_result(value_is_true))
+ def create_references(self, defer_payments=False):
+ self.db_set("status", "Pending")
+ self.db_set("error", None)
+ try:
+ self.check_fiscal_year()
+ company_settings = frappe.get_doc("FEC Import Settings", self.settings)
+
+ party = [l.party for l in self.gl_entries if l.party]
+ party_type = [l.party_type for l in self.gl_entries if l.party_type]
+ if len(party) == 1 and len(party_type) == 1:
+ if not self.is_payment_entry():
+ if company_settings.create_sales_invoices and party_type[0] == "Customer":
+ self.create_sales_invoice()
+ elif company_settings.create_sales_invoices and party_type[0] == "Supplier":
+ self.create_purchase_invoice()
+ else:
+ self.create_journal_entry()
+ else:
+ self.create_journal_entry(True if defer_payments else False)
+ else:
+ self.create_journal_entry()
+ except Exception:
+ frappe.db.rollback()
+ self.db_set("status", "Error")
+ self.db_set("error", frappe.get_traceback())
+
+ if defer_payments:
+ return True
+
+ def create_journal_entry(self, payment_entry=False):
+ journal_entry = frappe.get_doc(
+ {
+ "doctype": "Journal Entry",
+ "company": self.company,
+ "posting_date": self.gl_entries_date,
+ "cheque_no": self.pieceref or "N/A",
+ "cheque_date": self.gl_entries_date,
+ }
+ )
+ for line in self.gl_entries:
+ self.check_account_is_not_a_group(line.account)
+ reference_type, reference_name = self.get_payment_references(line)
+ journal_entry.append(
+ "accounts",
+ {
+ "accounting_journal": line.accounting_journal,
+ "account": line.account,
+ "debit_in_account_currency": line.debit,
+ "debit": line.debit,
+ "credit_in_account_currency": line.credit,
+ "credit": line.credit,
+ "user_remark": f"{line.ecriturelib}
{line.pieceref}",
+ "reference_type": reference_type,
+ "reference_name": reference_name,
+ "party_type": line.party_type,
+ "party": line.party,
+ "cost_center": frappe.get_cached_value("Company", self.company, "cost_center"),
+ },
+ )
+
+ if payment_entry and not reference_type and not reference_name:
+ frappe.throw(_("Payment references could not be found"))
+
+ if journal_entry.accounts:
+ journal_entry.insert()
+
+ if frappe.db.get_value("FEC Import Settings", self.settings, "submit_journal_entries"):
+ journal_entry.submit()
+
+ self.db_set("linked_document_type", "Journal Entry")
+ self.db_set("linked_document", journal_entry.name)
+ self.db_set("status", "Completed")
+
+ def is_payment_entry(self):
+ return [
+ l.account
+ for l in self.gl_entries
+ if frappe.get_cached_value("Account", l.account, "account_type") in ["Bank", "Cash"]
+ ]
+
+ def get_payment_references(self, line):
+ reference_type, reference_name = None, None
+ if line.ecriturelet:
+ account_root_type = frappe.get_cached_value("Account", line.account, "root_type")
+ if account_root_type == "Asset" and flt(line.debit) > 0:
+ return None, None
+ elif account_root_type == "Liability" and flt(line.credit) > 0:
+ return None, None
+
+ filters = dict(
+ name=("!=", line.name),
+ ecriturelet=line.ecriturelet,
+ comptenum=line.comptenum,
+ compauxnum=line.compauxnum,
+ datelet=line.datelet,
+ validdate=line.validdate,
+ )
+
+ if flt(line.credit) > 0.0:
+ filters["debit"] = line.credit
+ else:
+ filters["credit"] = line.debit
+
+ for doc in frappe.get_all("FEC Import Line", filters=filters, fields=["parent"]):
+ linked_document_type, linked_document = frappe.db.get_value(
+ "FEC Import Document", doc.parent, ["linked_document_type", "linked_document"]
+ )
+
+ if not (linked_document_type and linked_document):
+ continue
+
+ meta = frappe.get_meta(linked_document_type)
+ if meta.has_field("outstanding_amount"):
+ outstanding = frappe.db.get_value(linked_document_type, linked_document, "outstanding_amount")
+ if outstanding >= abs(line.debit - line.credit):
+ reference_type, reference_name = linked_document_type, linked_document
+
+ return reference_type, reference_name
+
+ def create_sales_invoice(self):
+ customer, debit_to, remark = self.get_party_and_party_account()
+
+ sales_invoice = frappe.new_doc("Sales Invoice")
+ sales_invoice.flags.ignore_permissions = True
+ sales_invoice.update(
+ {
+ "company": self.company,
+ "posting_date": self.gl_entries_date,
+ "set_posting_time": 1,
+ "customer": customer,
+ "debit_to": debit_to,
+ "accounting_journal": self.gl_entries[0].accounting_journal,
+ "remarks": remark,
+ "due_date": self.gl_entries_date,
+ }
+ )
+
+ self.add_items(sales_invoice)
+ self.add_taxes(sales_invoice)
+ sales_invoice.set_missing_values()
+
+ if sales_invoice.customer and sales_invoice.items:
+ # self.sales_invoices.append(sales_invoice)
+ try:
+ if self.pieceref:
+ sales_invoice.name = self.pieceref
+ sales_invoice.flags.draft_name_set = True
+ sales_invoice.insert()
+
+ if frappe.db.get_value("FEC Import Settings", self.settings, "submit_sales_invoices"):
+ if self.pieceref:
+ sales_invoice.flags.name_set = True
+
+ sales_invoice.flags.ignore_version = True
+ sales_invoice.submit()
+
+ self.db_set("linked_document_type", "Sales Invoice")
+ self.db_set("linked_document", sales_invoice.name)
+ self.db_set("status", "Completed")
+ except frappe.DuplicateEntryError:
+ print("Duplicate Invoice", sales_invoice.name)
+ self.db_set("linked_document_type", "Sales Invoice")
+ self.db_set("linked_document", sales_invoice.name)
+ self.db_set("status", "Completed")
+
+ else:
+ self.create_journal_entry()
+
+ def create_purchase_invoice(self):
+ supplier, credit_to, remark = self.get_party_and_party_account()
+
+ purchase_invoice = frappe.new_doc("Purchase Invoice")
+ purchase_invoice.flags.ignore_permissions = True
+ purchase_invoice.update(
+ {
+ "company": self.company,
+ "posting_date": self.gl_entries_date,
+ "set_posting_time": 1,
+ "supplier": supplier,
+ "credit_to": credit_to,
+ "accounting_journal": self.gl_entries[0].accounting_journal,
+ "remarks": remark,
+ }
+ )
+
+ self.add_items(purchase_invoice)
+ self.add_taxes(purchase_invoice)
+ purchase_invoice.set_missing_values()
+
+ if purchase_invoice.supplier and purchase_invoice.items:
+ # self.purchase_invoices.append(purchase_invoice)
+ try:
+ if self.pieceref:
+ purchase_invoice.name = self.pieceref
+ purchase_invoice.flags.draft_name_set = True
+ purchase_invoice.insert()
+
+ if frappe.db.get_value("FEC Import Settings", self.settings, "submit_sales_invoices"):
+ if self.pieceref:
+ purchase_invoice.flags.name_set = True
+
+ purchase_invoice.flags.ignore_version = True
+ purchase_invoice.submit()
+
+ self.db_set("linked_document_type", "Purchase Invoice")
+ self.db_set("linked_document", purchase_invoice.name)
+ self.db_set("status", "Completed")
+ except frappe.DuplicateEntryError:
+ print("Duplicate Invoice", purchase_invoice.name)
+ self.db_set("linked_document_type", "Purchase Invoice")
+ self.db_set("linked_document", purchase_invoice.name)
+ self.db_set("status", "Completed")
+
+ else:
+ self.create_journal_entry()
+
+ def get_party_and_party_account(self):
+ party_name = None
+ party_account = None
+ party_remark = None
+ for line in self.gl_entries:
+ if line.comptenum.startswith("40") or line.comptenum.startswith("41"):
+ if line.compauxnum:
+ party_name = line.party
+
+ party_account = line.account
+ party_remark = line.ecriturelib
+
+ break
+
+ return party_name, party_account, party_remark
+
+ def add_items(self, transaction):
+ for line in self.gl_entries:
+ if (
+ (line.comptenum.startswith("6") and line.debit > 0.0)
+ if transaction.doctype == "Purchase Invoice"
+ else (line.comptenum.startswith("7") and line.credit > 0.0)
+ ):
+ transaction.append(
+ "items",
+ {
+ "item_code": frappe.db.get_value(
+ "FEC Import Settings",
+ self.settings,
+ "purchase_item" if transaction.doctype == "Purchase Invoice" else "sales_item",
+ ),
+ "qty": 1,
+ "rate": line.credit if transaction.doctype == "Sales Invoice" else line.debit,
+ "income_account": line.account if transaction.doctype == "Sales Invoice" else None,
+ "expense_account": line.account if transaction.doctype == "Purchase Invoice" else None,
+ "remarks": line.ecriturelib,
+ },
+ )
+
+ self.check_account_is_not_a_group(line.account)
+
+ def add_taxes(self, transaction):
+ for line in self.gl_entries:
+ if (
+ (line.comptenum.startswith("6") and line.debit > 0.0)
+ if transaction.doctype == "Purchase Invoice"
+ else (line.comptenum.startswith("7") and line.credit > 0.0)
+ ):
+ continue
+
+ if line.comptenum.startswith("40") or line.comptenum.startswith("41"):
+ continue
+
+ amount = 0.0
+ if transaction.doctype == "Sales Invoice":
+ amount = line.credit
+ if not amount:
+ amount = flt(line.debit) * -1
+
+ elif transaction.doctype == "Purchase Invoice":
+ amount = flt(line.debit)
+ if not amount:
+ amount = flt(line.credit) * -1
+
+ transaction.append(
+ "taxes",
+ {
+ "charge_type": "Actual",
+ "account_head": line.account,
+ "tax_amount": amount,
+ "description": line.ecriturelib,
+ },
+ )
+
+ self.check_account_is_not_a_group(line.account)
+
+ def check_account_is_not_a_group(self, account):
+ if frappe.db.get_value("Account", account, "is_group"):
+ frappe.db.set_value("Account", account, "is_group", 0)
+
+
+def parse_date(date):
+ if not date:
+ return
+
+ return datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d")
+
+
+@frappe.whitelist()
+def bulk_process(docnames):
+ docnames = frappe.parse_json(docnames)
+ for docname in docnames:
+ doc = frappe.get_doc("FEC Import Document", docname)
+ if doc.status != "Completed":
+ doc.run_method("process_document_in_background")
diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document_list.js b/erpnext/regional/doctype/fec_import_document/fec_import_document_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..5b9fda2c274c0a29a406052f03013f1c32a9188a
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_document/fec_import_document_list.js
@@ -0,0 +1,20 @@
+frappe.listview_settings["FEC Import Document"] = {
+ onload: function (doclist) {
+ const action = () => {
+ const selected_docs = doclist.get_checked_items();
+ if (selected_docs.length > 0) {
+ let docnames = selected_docs.map((doc) => doc.name);
+ frappe.call({
+ method: "erpnext.regional.doctype.fec_import_document.fec_import_document.bulk_process",
+ args: { docnames },
+ }).then(() => {
+ frappe.show_alert({
+ message: __("Bulk processing in progress"),
+ indicator: "orange"
+ })
+ })
+ }
+ };
+ doclist.page.add_actions_menu_item(__("Bulk Process"), action, false);
+ },
+};
diff --git a/erpnext/regional/doctype/fec_import_document/test_fec_import_document.py b/erpnext/regional/doctype/fec_import_document/test_fec_import_document.py
new file mode 100644
index 0000000000000000000000000000000000000000..642c02a7b1d7a3903679860a8a312d87d98d357d
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_document/test_fec_import_document.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Dokos SAS and Contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestFECImportDocument(FrappeTestCase):
+ pass
diff --git a/erpnext/regional/doctype/fec_import_line/__init__.py b/erpnext/regional/doctype/fec_import_line/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.json b/erpnext/regional/doctype/fec_import_line/fec_import_line.json
new file mode 100644
index 0000000000000000000000000000000000000000..6c66431d5ed838f14ebb0bdf62156091542c508d
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.json
@@ -0,0 +1,258 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-06-14 09:55:52.985432",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "fec_data_section",
+ "journalcode",
+ "journallib",
+ "ecriturenum",
+ "ecrituredate",
+ "ecriturelib",
+ "column_break_7",
+ "comptenum",
+ "comptelib",
+ "compauxnum",
+ "compauxlib",
+ "pieceref",
+ "piecedate",
+ "column_break_14",
+ "debit",
+ "credit",
+ "ecriturelet",
+ "datelet",
+ "validdate",
+ "montantdevise",
+ "idevise",
+ "dokos_data_section",
+ "accounting_journal",
+ "account",
+ "column_break_25",
+ "party_type",
+ "party",
+ "dokos_dates_section",
+ "posting_date",
+ "transaction_date",
+ "column_break_31",
+ "validation_date",
+ "reconciliation_date",
+ "section_break_22",
+ "hashed_data"
+ ],
+ "fields": [
+ {
+ "fieldname": "journalcode",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "JournalCode",
+ "read_only": 1
+ },
+ {
+ "fieldname": "journallib",
+ "fieldtype": "Small Text",
+ "label": "JournalLib",
+ "read_only": 1
+ },
+ {
+ "fieldname": "ecriturenum",
+ "fieldtype": "Data",
+ "label": "EcritureNum",
+ "read_only": 1
+ },
+ {
+ "fieldname": "ecrituredate",
+ "fieldtype": "Data",
+ "label": "EcritureDate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "comptenum",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "CompteNum",
+ "read_only": 1
+ },
+ {
+ "fieldname": "comptelib",
+ "fieldtype": "Small Text",
+ "label": "CompteLib",
+ "read_only": 1
+ },
+ {
+ "fieldname": "compauxnum",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "CompAuxNum",
+ "read_only": 1
+ },
+ {
+ "fieldname": "compauxlib",
+ "fieldtype": "Small Text",
+ "label": "CompAuxLib",
+ "read_only": 1
+ },
+ {
+ "fieldname": "pieceref",
+ "fieldtype": "Data",
+ "label": "PieceRef",
+ "read_only": 1
+ },
+ {
+ "fieldname": "piecedate",
+ "fieldtype": "Data",
+ "label": "PieceDate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "ecriturelib",
+ "fieldtype": "Small Text",
+ "label": "EcritureLib",
+ "read_only": 1
+ },
+ {
+ "fieldname": "debit",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Debit",
+ "read_only": 1
+ },
+ {
+ "fieldname": "credit",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Credit",
+ "read_only": 1
+ },
+ {
+ "fieldname": "ecriturelet",
+ "fieldtype": "Data",
+ "label": "EcritureLet",
+ "read_only": 1
+ },
+ {
+ "fieldname": "datelet",
+ "fieldtype": "Data",
+ "label": "DateLet",
+ "read_only": 1
+ },
+ {
+ "fieldname": "validdate",
+ "fieldtype": "Data",
+ "label": "ValidDate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "montantdevise",
+ "fieldtype": "Float",
+ "label": "Montantdevise",
+ "read_only": 1
+ },
+ {
+ "fieldname": "idevise",
+ "fieldtype": "Data",
+ "label": "Idevise",
+ "read_only": 1
+ },
+ {
+ "fieldname": "fec_data_section",
+ "fieldtype": "Section Break",
+ "label": "FEC Data"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_22",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "dokos_data_section",
+ "fieldtype": "Section Break",
+ "hide_border": 1,
+ "label": "Dokos Data"
+ },
+ {
+ "fieldname": "accounting_journal",
+ "fieldtype": "Link",
+ "label": "Accounting Journal",
+ "options": "Accounting Journal"
+ },
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "label": "Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "column_break_25",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "party_type",
+ "fieldtype": "Link",
+ "label": "Party Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "party",
+ "fieldtype": "Dynamic Link",
+ "label": "Party",
+ "options": "party_type"
+ },
+ {
+ "fieldname": "dokos_dates_section",
+ "fieldtype": "Section Break",
+ "label": "Dokos Dates"
+ },
+ {
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date"
+ },
+ {
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "label": "Transaction Date"
+ },
+ {
+ "fieldname": "column_break_31",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "validation_date",
+ "fieldtype": "Date",
+ "label": "Validation Date"
+ },
+ {
+ "fieldname": "reconciliation_date",
+ "fieldtype": "Date",
+ "label": "Reconciliation Date"
+ },
+ {
+ "fieldname": "hashed_data",
+ "fieldtype": "Data",
+ "label": "Hashed Data",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-06-21 16:32:37.859603",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "FEC Import Line",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.py b/erpnext/regional/doctype/fec_import_line/fec_import_line.py
new file mode 100644
index 0000000000000000000000000000000000000000..d48e0eec9c05c37601564bef35edab9f1812fd34
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2023, Dokos SAS and contributors
+# For license information, please see license.txt
+
+from frappe.model.document import Document
+
+
+class FECImportLine(Document):
+ pass
diff --git a/erpnext/regional/doctype/fec_import_settings/__init__.py b/erpnext/regional/doctype/fec_import_settings/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/erpnext/regional/doctype/fec_import_settings/fec_import_settings.js b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..d26d80c42c6ec33f0bb33d88b45dac3cfb6f2b02
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.js
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, Dokos SAS and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('FEC Import Settings', {
+ refresh: function(frm) {
+ if (!frm.is_new()) {
+ frm.add_custom_button(__("Import a FEC"), () => {
+ frappe.new_doc('FEC Import', {company: frm.doc.company});
+ })
+ }
+ }
+});
diff --git a/erpnext/regional/doctype/fec_import_settings/fec_import_settings.json b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..9c109fb49c9b0d600556245aaa13f6e677fb2b79
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.json
@@ -0,0 +1,214 @@
+{
+ "actions": [],
+ "autoname": "field:company",
+ "beta": 1,
+ "creation": "2023-06-08 18:04:51.693692",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "links_tab",
+ "section_break_2",
+ "company",
+ "accounting_journal_mapping",
+ "journal_entries_tab",
+ "submit_journal_entries",
+ "sales_invoices_tab",
+ "create_sales_invoices",
+ "submit_sales_invoices",
+ "sales_item",
+ "purchase_invoices_tab",
+ "create_purchase_invoices",
+ "submit_purchase_invoices",
+ "purchase_item",
+ "parties_tab",
+ "suppliers_section",
+ "supplier_group",
+ "section_break_nquh",
+ "customers_column",
+ "customer_group",
+ "territory"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "section_break_2",
+ "fieldtype": "Tab Break",
+ "label": "Accounting Journal"
+ },
+ {
+ "fieldname": "accounting_journal_mapping",
+ "fieldtype": "Table",
+ "label": "Accounting Journal Mapping",
+ "options": "FEC Accounting Journal Mapping"
+ },
+ {
+ "fieldname": "sales_invoices_tab",
+ "fieldtype": "Tab Break",
+ "label": "Sales Invoices"
+ },
+ {
+ "depends_on": "eval:doc.create_sales_invoices",
+ "fieldname": "sales_item",
+ "fieldtype": "Link",
+ "label": "Sales Item",
+ "mandatory_depends_on": "eval:doc.create_sales_invoices",
+ "options": "Item"
+ },
+ {
+ "fieldname": "purchase_invoices_tab",
+ "fieldtype": "Tab Break",
+ "label": "Purchase Invoices"
+ },
+ {
+ "depends_on": "eval:doc.create_purchase_invoices",
+ "fieldname": "purchase_item",
+ "fieldtype": "Link",
+ "label": "Purchase Item",
+ "mandatory_depends_on": "eval:doc.create_purchase_invoices",
+ "options": "Item"
+ },
+ {
+ "default": "0",
+ "fieldname": "create_sales_invoices",
+ "fieldtype": "Check",
+ "label": "Create Sales Invoices"
+ },
+ {
+ "default": "0",
+ "fieldname": "create_purchase_invoices",
+ "fieldtype": "Check",
+ "label": "Create Purchase Invoices"
+ },
+ {
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Default Customer Group",
+ "options": "Customer Group",
+ "reqd": 1
+ },
+ {
+ "fieldname": "supplier_group",
+ "fieldtype": "Link",
+ "label": "Default Supplier Group",
+ "options": "Supplier Group",
+ "reqd": 1
+ },
+ {
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Default Territory",
+ "options": "Territory",
+ "reqd": 1
+ },
+ {
+ "fieldname": "links_tab",
+ "fieldtype": "Tab Break",
+ "label": "Links",
+ "show_dashboard": 1
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:doc.create_sales_invoices",
+ "fieldname": "submit_sales_invoices",
+ "fieldtype": "Check",
+ "label": "Automatically Submit Sales Invoices"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:doc.create_purchase_invoices",
+ "fieldname": "submit_purchase_invoices",
+ "fieldtype": "Check",
+ "label": "Automatically Submit Purchase Invoices"
+ },
+ {
+ "fieldname": "journal_entries_tab",
+ "fieldtype": "Tab Break",
+ "label": "Journal Entries"
+ },
+ {
+ "default": "1",
+ "fieldname": "submit_journal_entries",
+ "fieldtype": "Check",
+ "label": "Automatically Submit Journal Entries"
+ },
+ {
+ "fieldname": "parties_tab",
+ "fieldtype": "Tab Break",
+ "label": "Parties"
+ },
+ {
+ "fieldname": "suppliers_section",
+ "fieldtype": "Section Break",
+ "label": "Suppliers"
+ },
+ {
+ "fieldname": "section_break_nquh",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "customers_column",
+ "fieldtype": "Column Break",
+ "label": "Customers"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [
+ {
+ "group": "Import Configuration",
+ "link_doctype": "FEC Import",
+ "link_fieldname": "import_settings"
+ },
+ {
+ "group": "Imported Documents",
+ "link_doctype": "FEC Import Document",
+ "link_fieldname": "settings"
+ }
+ ],
+ "modified": "2023-08-24 15:03:46.413291",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "FEC Import Settings",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "select": 1,
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/erpnext/regional/doctype/fec_import_settings/fec_import_settings.py b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bfc28df8b7d830d2752dab3e099593f0fa1453d
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_settings/fec_import_settings.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2023, Dokos SAS and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class FECImportSettings(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ from erpnext.regional.doctype.fec_accounting_journal_mapping.fec_accounting_journal_mapping import (
+ FECAccountingJournalMapping,
+ )
+
+ accounting_journal_mapping: DF.Table[FECAccountingJournalMapping]
+ company: DF.Link
+ create_purchase_invoices: DF.Check
+ create_sales_invoices: DF.Check
+ customer_group: DF.Link
+ purchase_item: DF.Link | None
+ sales_item: DF.Link | None
+ submit_journal_entries: DF.Check
+ submit_purchase_invoices: DF.Check
+ submit_sales_invoices: DF.Check
+ supplier_group: DF.Link
+ territory: DF.Link
+ # end: auto-generated types
+ pass
diff --git a/erpnext/regional/doctype/fec_import_settings/test_fec_import_settings.py b/erpnext/regional/doctype/fec_import_settings/test_fec_import_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d8abcc47f80d5ab7de6caa29679ababbebbe80f
--- /dev/null
+++ b/erpnext/regional/doctype/fec_import_settings/test_fec_import_settings.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Dokos SAS and Contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestFECImportSettings(FrappeTestCase):
+ pass
diff --git a/erpnext/regional/france/setup.py b/erpnext/regional/france/setup.py
index a22238892a2a7c7654308356c20e660bdea9b842..d4df0714eae26499a85f20f007bba95beeb46bef 100644
--- a/erpnext/regional/france/setup.py
+++ b/erpnext/regional/france/setup.py
@@ -143,7 +143,7 @@ def default_accounts_mapping(accounts, company):
"capital_work_in_progress_account": 231,
"asset_received_but_not_billed": 722,
"default_advance_received_account": 4191,
- "default_down_payment_payable_account": 4091,
+ "default_advance_paid_account": 4091,
}
return {
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 8b8f55649bd4487cd9d8a9ba2768c9af34d6437d..30e7e6b53aebc72c9c2c3f84fdf2e0ad297dfb68 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -3241,7 +3241,7 @@ Default Payroll Payable Account,Compte de paie par défaut,
Default Price List,Liste de prix par défaut,
Default Priority,Priorité par défaut,
Default Provisional Account,Compte provisionnel par défaut,
-Default Purchase Unit of Measure,Unité de Mesure par défaut à l'Achat,
+Default Purchase Unit of Measure,Unité de Mesure par défaut à l'achat,
Default Quotation Validity Days,N° de jours de validité par défaut pour les devis,
Default Receivable Account,Compte client par défaut,
Default Receivable Accounts,Comptes débiteurs par défaut,
@@ -3256,7 +3256,7 @@ Default Service Level Agreement,Accord de niveau de service par défaut,
Default Shift,Quart par défaut,
Default Shipping Account,Compte d'expédition par défaut,
Default Source Warehouse,Entrepôt source par défaut,
-Default Stock UOM,Unité de mesure par Défaut des Articles,
+Default Stock UOM,Unité de mesure par défaut des articles,
Default Supplier Group,Groupe de fournisseurs par défaut,
Default Supplier,Fournisseur par défaut,
Default Target Warehouse,Entrepôt cible par défaut,
@@ -3266,8 +3266,8 @@ Default Territory,Région par défaut,
Default UOM,Unité de mesure par défaut,
Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item.,"L'unité de mesure par défaut de l'élément {0} ne peut pas être modifiée directement car vous avez déjà effectué une ou plusieurs transactions avec une autre UOM. Vous devez soit annuler les documents liés, soit créer un nouvel élément.",
Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,L'Unité de Mesure par Défaut pour l'Article {0} ne peut pas être modifiée directement parce que vous avez déjà fait une (des) transaction (s) avec une autre unité de mesure. Vous devez créer un nouvel article pour utiliser une Unité de mesure par défaut différente.,
-Default Unit of Measure for Variant '{0}' must be same as in Template '{1}',L'Unité de mesure par défaut pour la variante '{0}' doit être la même que dans le Modèle '{1}',
-Default Unit of Measure,Unité de Mesure par Défaut,
+Default Unit of Measure for Variant '{0}' must be same as in Template '{1}',L'unité de mesure par défaut pour la variante '{0}' doit être la même que dans le Modèle '{1}',
+Default Unit of Measure,Unité de mesure par défaut,
Default Valuation Method,Méthode de Valorisation par Défaut,
Default Value,Valeur par défaut,
Default Values,Valeurs Par Défaut,
@@ -9148,8 +9148,8 @@ Row #{0}: Finished Good Item Qty is not specified for service item {0},Ligne #{0
Row #{0}: Finished Good Item is not specified for service item {1},Ligne #{0}: L'article de produit fini n'est pas spécifié pour l'article de service {1},
Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2},Ligne #{0}: L'article de produit fini {1} doit être un article sous-traité pour l'article de service {2},
Row #{0}: Finished Good Item {1} must be a sub-contracted item,Ligne #{0}: L'article produit fini {1} doit être un article de sous-traitance,
-"Row #{0}: For {1}, you can select reference document only if account gets credited","Ligne #{0}: Pour {1}, vous pouvez sélectionner un document de référence seulement si le compte est crédité",
-"Row #{0}: For {1}, you can select reference document only if account gets debited","Ligne #{0}: Pour {1}, vous pouvez sélectionner un document de référence seulement si le compte est débité",
+"Row #{0}: For {1}, you can select a reference document only if account gets credited","Ligne #{0}: Pour {1}, vous pouvez sélectionner un document de référence seulement si le compte est crédité",
+"Row #{0}: For {1}, you can select a reference document only if account gets debited","Ligne #{0}: Pour {1}, vous pouvez sélectionner un document de référence seulement si le compte est débité",
Row #{0}: From Date cannot be before To Date,Ligne #{0} : La date de début ne peut pas être antérieure à la date de fin,
Row #{0}: Item added,Ligne #{0}: Article ajouté,
Row #{0}: Item {1} does not exist,Ligne #{0} : L'élément {1} n'existe pas,
@@ -9294,7 +9294,7 @@ Row {0}: Packed Qty must be equal to {1} Qty.,Ligne {0}: La qté emballée doit
Row {0}: Packing Slip is already created for Item {1}.,Ligne {0}: le bordereau de colis est déjà créé pour l'article {1},
Row {0}: Paid amount {1} is greater than pending accrued amount {2} against loan {3},Ligne {0}: Le montant payé {1} est supérieur au au montant accumulé restant {2} pour le prêt {3},
Row {0}: Paid amount {1} is greater than pending accrued amount {2}against loan {3},Ligne {0}: Le montant payé {1} est supérieur au montant accumulé en attente {2} pour le prêt {3},
-Row {0}: Party / Account does not match with {1} / {2} in {3} {4},Ligne {0} : Tiers / Compte ne correspond pas à {1} / {2} en {3} {4},
+Row {0}: Party / Account does not match with {1} / {2} in {3} {4},Ligne {0} : Le tiers / compte ne correspond pas à {1} / {2} pour le·la {3} {4},
Row {0}: Party Type and Party is required for Receivable / Payable account {1},Ligne {0} : Le Type de tiers et le Tiers sont requis pour le compte Débiteur / Créditeur {1},
Row {0}: Payment against Sales/Purchase Order should always be marked as advance,Ligne {0} : Les paiements contre des commandes client / fournisseur doivent toujours être marqués comme des avances,
Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.,Ligne {0} : Veuillez vérifier 'Est Avance' sur le compte {1} si c'est une avance.,