E-METR (Unlocker)

It's a simple hack for editing contracts, unblock dates of start testing, restore old services

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         E-METR (Unlocker)
// @namespace    http://tampermonkey.net/
// @version      1.6.1
// @author       NX
// @icon         https://xodim.e-metrologiya.uz/back/app-assets/images/ico/favicon.ico
// @description  It's a simple hack for editing contracts, unblock dates of start testing, restore old services
// @match        https://xodim.e-metrologiya.uz/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {

'use strict';

/* ============================================================
   UTILFO
============================================================ */

const waitVue = () => new Promise(resolve => {
    const t = setInterval(() => {
        const app = document.querySelector("#app")?.__vue__;
        if (app) { clearInterval(t); resolve(app); }
    }, 300);
});

const toast = msg => {
    const el = document.createElement("div");
    el.innerText = msg;
    el.style = `
      position: fixed; top: 20px; left: 20px;
      background: #000; color: #fff;
      padding: 10px 16px; border-radius: 8px;
      font-size: 15px; z-index: 999999999;
      opacity: 0; transition: .25s;
    `;
    document.body.appendChild(el);
    setTimeout(() => el.style.opacity = 1, 40);
    setTimeout(() => { el.style.opacity = 0; setTimeout(() => el.remove(), 300); }, 1700);
};

/* ============================================================
   YANGI XIZMAT TURLARI
============================================================ */

const NEW = [
    { id: 4, name: "Ўлчаш воситаларини қиёслаш" },
    { id: 6, name: "Синов воситаларини метрологик аттестациядан ўтказиш" },
    { id: 7, name: "Ўлчаш воситаларини калибрлаш" },
    { id: 20, name: "Ўлчаш комплексларини метрологик аттестациядан ўтказиш" }
  ];

/* ============================================================
   OFFLINE APPEAL — xizmatlarni tiklash
============================================================ */

function findSelect(vm, arr = []) {
    if (!vm) return arr;
    if (vm.$options?.name === "vue-multiselect") arr.push(vm);
    vm.$children?.forEach(c => findSelect(c, arr));
    return arr;
  }

  async function setNew() {
    const app = await waitVue();
    const list = findSelect(app);
    if (!list.length) return toast("❗ Xatolik: multiselect topilmadi!");

    const ms = list[1] || list[0];
    const parents = [ms.$parent, ms.$parent?.$parent, ms.$parent?.$parent?.$parent];

    parents.forEach(p => {
      if (!p) return;
      if (p.types) p.types = NEW;
      if (p.device?.types) p.device.types = NEW;
      if (p.form?.types) p.form.types = NEW;
    });

    ms.options = NEW;
    if (ms.$data) ms.$data.internalOptions = NEW;
    ms.$forceUpdate();
    ms.$parent?.$forceUpdate();

    toast("✅ Xizmatlar tiklandi!");
  }

/* ============================================================
   VDP DATEPICKER — sanalarni ochish (VDP)
============================================================ */

async function unlockDates() {
    const app = await waitVue();

    function findPickers(vm, arr = []) {
        if (!vm) return arr;
        if (
            vm.$options?.name?.includes("datepicker") ||
            vm.$el?.classList?.contains("vdp-datepicker")
        ) arr.push(vm);

        vm.$children?.forEach(c => findPickers(c, arr));
        return arr;
    }

    const pickers = findPickers(app);

    pickers.forEach(dp => {
        dp.isDateDisabled = () => false;
        dp.disabledDates = [];
        dp.$forceUpdate();
    });

    document.querySelectorAll(".vdp-datepicker__calendar .disabled").forEach(el => {
        el.classList.remove("disabled");
        el.style.opacity = "1";
        el.style.pointerEvents = "auto";
        el.style.cursor = "pointer";
    });

    toast("📅 Sanalar to'liq ochildi!");
}

/* ============================================================
   FLATPICKR — “Дата поверки”
============================================================ */

function unlockFlatpickr2() {

    const fpInput = document.querySelector('#datePicker');
    if (!fpInput) return toast("❗ Bu funksiya boshqa sertifikat turi uchun!");

    const fp = fpInput._flatpickr;
    if (!fp) return toast("❗ Flatpickr instance topilmadi!");

    // 1) CHEKLOVLARNI O‘CHIRAMIZ
    fp.set('minDate', null);
    fp.set('maxDate', null);

    // 2) Blocklangan sanalarni tozalaymiz
    fp.set('disable', []);
    fp.set('enable', []);

    // 3) Oylik ko‘rinish — bitta oy
    fp.set('showMonths', 1);

    // 4) Oylab tanlash — dropdown
    fp.set('monthSelectorType', 'dropdown');

    // 5) readonly olib tashlanadi
    fpInput.removeAttribute('readonly');

    // 6) qayta chizamiz
    fp.redraw();

    toast("📅 Sanalar to‘liq ochildi!");
}

/* ============================================================
   APPLICATION UPDATE — sizning original patch
============================================================ */

function patchApplicationMultiselects() {

    function findVue(vm, arr = []) {
        if (!vm) return arr;
        if (vm.$options?.name === "vue-multiselect") arr.push(vm);
        vm.$children?.forEach(c => findVue(c, arr));
        return arr;
    }

    const app = document.querySelector("#app")?.__vue__;
    if (!app) return toast("Vue topilmadi!");

    const selects = findVue(app);

    selects.forEach(ms => {
        ms.options = NEW;
        if (ms.$data) ms.$data.internalOptions = NEW;
        ms.$forceUpdate();
    });

    toast("🔧 Xizmatlar tiklandi!");
}

/* ============================================================
   PANEL WIDGET
============================================================ */

function createPanel(content) {

    const toggle = document.createElement("div");
    toggle.innerHTML = "⚙️";
    toggle.style = `
      position:fixed;bottom:20px;left:20px;width:48px;height:48px;
      background:#1976d2;color:#fff;border-radius:12px;
      display:flex;align-items:center;justify-content:center;
      font-size:26px;cursor:pointer;z-index:999999999;
      box-shadow:0 3px 14px rgba(0,0,0,.35);
    `;
    document.body.appendChild(toggle);

    const panel = document.createElement("div");
    panel.style = `
      position:fixed;bottom:80px;left:20px;width:260px;padding:14px;
      background:#fff;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,.25);
      display:none;z-index:999999999;
    `;
    panel.innerHTML = content;
    document.body.appendChild(panel);

    toggle.onclick = () =>
        panel.style.display =
        panel.style.display === "none" ? "block" : "none";
}

/* ============================================================
   ROUTING
============================================================ */

const url = location.href;

/* OFFLINE APPEAL */
if (url.includes("/reviewer/offline-appeal/")) {
    createPanel(`
      <button id="btn-new"
        style="width:100%;padding:10px;background:#1976d2;color:white;border-radius:8px;">
        ✅ Xizmatlarni tiklash
      </button>
    `);
    document.getElementById("btn-new").onclick = setNew;
}

/* CONTRACTS */
else if (url.includes("/reviewer/contracts")) {
    createPanel(`<div style="font-weight:bold;font-size:14px;margin-bottom:6px;">📄 Shartnomalarni o‘zgartirish</div>
      <input id="appId" placeholder="Ariza raqami..." style="width:100%;padding:8px;margin-bottom:8px;">
      <button id="btn-open" style="width:100%;padding:10px;background:#0288d1;color:white;border-radius:8px;">
        🔗 Ochish
      </button>
    `);

    document.getElementById("btn-open").onclick = () => {
        const id = document.getElementById("appId").value.trim();
        if (!id) return alert("Raqam kiriting!");
        window.open(`https://xodim.e-metrologiya.uz/reviewer/application/${id}/update?update=${id}&comment=null`, "_blank");
    };
}

/* CERTIFICATE (VDP + Flatpickr)*/
else if (url.includes("/comparator/certificate/")) {
    createPanel(`
      <button id="btn-unlock"
        style="width:100%;padding:10px;background:#2e7d32;color:white;border-radius:8px;margin-bottom:8px;">
        📅 Sanalarni ochish <br>(asosiy)
      </button>

      <button id="btn-unlock2"
        style="width:100%;padding:10px;background:#3949ab;color:white;border-radius:8px;">
        📅 Sanalarni ochish <br>(boshqa turdagi)
      </button>
    `);

    document.getElementById("btn-unlock").onclick = unlockDates;
    document.getElementById("btn-unlock2").onclick = unlockFlatpickr2;
}

/* APPLICATION UPDATE */
else if (url.includes("/reviewer/application/")) {

    createPanel(`
      <div style="font-weight:bold;text-align:center;margin-bottom:10px;color:#4a148c;">
        ⚠️ Har xizmat tanlanganda va yaratilganda takrorlang
      </div>
      <button id="btn-patch"
        style="width:100%;padding:12px;background:#8e24aa;color:white;border:none;border-radius:10px;margin-bottom:8px;">
        🔧 Xizmatlarni tiklash
      </button>

    `);

    document.getElementById("btn-patch").onclick = patchApplicationMultiselects;
    document.getElementById("btn-flat").onclick = unlockFlatpickr2;
}

})();