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",
|
"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",
|
||||||
|
|
|
||||||
|
|
@ -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