added load from cbar currency import
This commit is contained in:
parent
eb62cafbcf
commit
a66945f7d7
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -29,6 +29,10 @@ doctype_js = {
|
|||
"Bank Reconciliation Tool": "public/js/bank_reconciliation_tool.js",
|
||||
}
|
||||
|
||||
doctype_list_js = {
|
||||
"Currency Exchange": "public/js/currency_exchange_list.js",
|
||||
}
|
||||
|
||||
override_doctype_class = {
|
||||
"Bulk Salary Structure Assignment": "jey_erp.custom.bulk_salary_structure_assignment.CustomBulkSalaryStructureAssignment",
|
||||
"Payroll Entry": "jey_erp.custom.payroll_entry.CustomPayrollEntry",
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
}
|
||||
Loading…
Reference in New Issue