added load from cbar currency import

This commit is contained in:
Ali 2026-03-12 18:53:47 +04:00
parent eb62cafbcf
commit a66945f7d7
3 changed files with 253 additions and 0 deletions

148
jey_erp/cbar_exchange.py Normal file
View File

@ -0,0 +1,148 @@
import json
import random
import time
import xml.etree.ElementTree as ET
from datetime import timedelta
import requests
import frappe
from frappe.utils import getdate
CBAR_URL = "https://www.cbar.az/currencies/{}.xml"
TO_CURRENCY = "AZN"
@frappe.whitelist()
def get_cbar_currencies():
"""Return list of currency codes available on CBAR (Xarici valyutalar only),
intersected with currencies that exist in ERPNext."""
from datetime import date as _date
today = _date.today().strftime("%d.%m.%Y")
try:
resp = requests.get(CBAR_URL.format(today), timeout=15)
resp.raise_for_status()
root = ET.fromstring(resp.content)
except Exception as e:
frappe.log_error(title="CBAR get_currencies failed", message=str(e))
frappe.throw(frappe._("Could not fetch currency list from CBAR"))
existing = set(frappe.db.get_all("Currency", filters={"enabled": 1}, pluck="name"))
result = []
for val_type in root.findall("ValType"):
if val_type.get("Type", "") != "Xarici valyutalar":
continue
for valute in val_type.findall("Valute"):
code = valute.get("Code")
if code and code in existing:
result.append(code)
return result
@frappe.whitelist()
def fetch_cbar_rates(from_date, to_date, currencies=None):
from_date = getdate(from_date)
to_date = getdate(to_date)
if from_date > to_date:
frappe.throw(frappe._("From Date cannot be after To Date"))
if isinstance(currencies, str):
currencies = json.loads(currencies)
user = frappe.session.user
frappe.enqueue(
_import_in_background,
queue="long",
timeout=600,
from_date=from_date,
to_date=to_date,
user=user,
currencies=currencies or [],
)
return "enqueued"
def _import_in_background(from_date, to_date, user, currencies):
created, updated, skipped = 0, 0, []
dates = []
d = from_date
while d <= to_date:
dates.append(d)
d += timedelta(days=1)
total = len(dates)
# Cache which currencies actually exist in ERPNext
existing_currencies = set(
frappe.db.get_all("Currency", filters={"enabled": 1}, pluck="name")
)
for idx, current in enumerate(dates):
date_cbar = current.strftime("%d.%m.%Y")
date_db = current.strftime("%Y-%m-%d")
frappe.publish_realtime(
"cbar_import_progress",
{"current": idx + 1, "total": total, "date": date_cbar},
user=user,
)
try:
resp = requests.get(CBAR_URL.format(date_cbar), timeout=15)
resp.raise_for_status()
root = ET.fromstring(resp.content)
for val_type in root.findall("ValType"):
for valute in val_type.findall("Valute"):
code = valute.get("Code")
# Filter by user-selected currencies (empty = all)
if currencies and code not in currencies:
continue
# Skip currencies not in ERPNext
if code not in existing_currencies:
continue
nominal_raw = valute.findtext("Nominal", "1")
nominal = int("".join(filter(str.isdigit, nominal_raw)) or "1")
value = float(valute.findtext("Value", "0"))
if not nominal or not value:
continue
rate = value / nominal
doc_name = f"{date_db}-{code}-{TO_CURRENCY}-Selling-Buying"
if frappe.db.exists("Currency Exchange", doc_name):
frappe.db.set_value("Currency Exchange", doc_name, "exchange_rate", rate)
updated += 1
else:
doc = frappe.new_doc("Currency Exchange")
doc.date = date_db
doc.from_currency = code
doc.to_currency = TO_CURRENCY
doc.exchange_rate = rate
doc.for_buying = 1
doc.for_selling = 1
doc.insert(ignore_permissions=True)
created += 1
frappe.db.commit()
except Exception as e:
frappe.log_error(title=f"CBAR fetch failed: {date_cbar}", message=str(e))
skipped.append(date_cbar)
if idx < total - 1:
time.sleep(random.uniform(0.3, 0.5))
frappe.publish_realtime(
"cbar_import_complete",
{"created": created, "updated": updated, "skipped_dates": skipped},
user=user,
)

View File

@ -29,6 +29,10 @@ doctype_js = {
"Bank Reconciliation Tool": "public/js/bank_reconciliation_tool.js", "Bank Reconciliation Tool": "public/js/bank_reconciliation_tool.js",
} }
doctype_list_js = {
"Currency Exchange": "public/js/currency_exchange_list.js",
}
override_doctype_class = { override_doctype_class = {
"Bulk Salary Structure Assignment": "jey_erp.custom.bulk_salary_structure_assignment.CustomBulkSalaryStructureAssignment", "Bulk Salary Structure Assignment": "jey_erp.custom.bulk_salary_structure_assignment.CustomBulkSalaryStructureAssignment",
"Payroll Entry": "jey_erp.custom.payroll_entry.CustomPayrollEntry", "Payroll Entry": "jey_erp.custom.payroll_entry.CustomPayrollEntry",

View File

@ -0,0 +1,101 @@
frappe.listview_settings["Currency Exchange"] = {
onload: function (listview) {
listview.page.add_inner_button(__("Import CBAR Rates"), function () {
// Fetch available currencies from CBAR first, then show dialog
frappe.call({
method: "jey_erp.cbar_exchange.get_cbar_currencies",
callback: function (r) {
const available = r.message || [];
_show_import_dialog(available);
}
});
});
}
};
function _show_import_dialog(available_currencies) {
const today = frappe.datetime.get_today();
// Build lookup for fast filtering in get_data
const currency_set = new Set(available_currencies);
const d = new frappe.ui.Dialog({
title: __("Import CBAR Exchange Rates"),
fields: [
{ fieldname: "from_date", fieldtype: "Date", label: __("From Date"), reqd: 1, default: today },
{ fieldname: "to_date", fieldtype: "Date", label: __("To Date"), reqd: 1, default: today },
{
fieldname: "currencies",
fieldtype: "MultiSelectList",
label: __("Currencies"),
reqd: 1,
get_data: function (txt) {
return available_currencies
.filter(c => !txt || c.toLowerCase().includes(txt.toLowerCase()))
.map(c => ({ value: c, description: c }));
}
},
{
fieldname: "note", fieldtype: "HTML",
options: `<p class="text-muted small">${__("Rates are loaded from cbar.az for each day in the range. Existing records will be updated.")}</p>`
}
],
primary_action_label: __("Import"),
primary_action: function (values) {
const currencies = values.currencies;
if (!currencies || !currencies.length) {
frappe.msgprint(__("Please select at least one currency."));
return;
}
d.hide();
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.realtime.on("cbar_import_progress", function (data) {
frappe.show_progress(
__("Importing CBAR Rates"),
data.current,
data.total,
__("Loading {0} ({1} of {2})", [data.date, data.current, data.total])
);
});
frappe.realtime.on("cbar_import_complete", function (data) {
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.hide_progress();
const { created, updated, skipped_dates } = data;
let msg = `<b>${__("Import complete")}</b><br>${__("Created")}: ${created}<br>${__("Updated")}: ${updated}`;
if (skipped_dates && skipped_dates.length)
msg += `<br><span class="text-danger">${__("Failed dates")}: ${skipped_dates.join(", ")}</span>`;
frappe.msgprint({
title: __("CBAR Import Result"),
message: msg,
indicator: skipped_dates && skipped_dates.length ? "orange" : "green"
});
cur_list && cur_list.refresh();
});
frappe.call({
method: "jey_erp.cbar_exchange.fetch_cbar_rates",
args: {
from_date: values.from_date,
to_date: values.to_date,
currencies: currencies
},
error: function () {
frappe.realtime.off("cbar_import_progress");
frappe.realtime.off("cbar_import_complete");
frappe.hide_progress();
}
});
}
});
d.show();
// Default: USD only
d.set_value("currencies", ["USD"]);
}