added new report Fuel Products Purchase Report
This commit is contained in:
parent
10287dd7e2
commit
537289c230
|
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) 2024, Your Company and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Fuel Products Purchase Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
"reqd": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1,
|
||||
"width": "100px"
|
||||
}
|
||||
],
|
||||
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
// Подсветка колонок с топливом
|
||||
if (["ai_92", "ai_95", "ai_80", "dizel", "maye_qaz"].includes(column.fieldname)) {
|
||||
if (value && parseFloat(value) > 0) {
|
||||
// Разные цвета для разных типов топлива
|
||||
let color = "#28a745"; // зеленый по умолчанию
|
||||
|
||||
switch(column.fieldname) {
|
||||
case "ai_92":
|
||||
color = "#28a745"; // зеленый
|
||||
break;
|
||||
case "ai_95":
|
||||
color = "#007bff"; // синий
|
||||
break;
|
||||
case "ai_80":
|
||||
color = "#6c757d"; // серый
|
||||
break;
|
||||
case "dizel":
|
||||
color = "#fd7e14"; // оранжевый
|
||||
break;
|
||||
case "maye_qaz":
|
||||
color = "#e83e8c"; // розовый
|
||||
break;
|
||||
}
|
||||
|
||||
value = `<span style="color: ${color}; font-weight: bold; background-color: ${color}15; padding: 2px 6px; border-radius: 3px;">${value}</span>`;
|
||||
} else if (value == 0 || !value) {
|
||||
value = `<span style="color: #ccc;">0</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Подсветка Total Qty
|
||||
if (column.fieldname == "total_qty" && value) {
|
||||
let qty = parseFloat(value);
|
||||
if (qty > 50) {
|
||||
value = `<span style="color: #dc3545; font-weight: bold;">${value}</span>`;
|
||||
} else if (qty > 20) {
|
||||
value = `<span style="color: #ffc107; font-weight: bold;">${value}</span>`;
|
||||
} else if (qty > 0) {
|
||||
value = `<span style="color: #28a745; font-weight: bold;">${value}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
"onload": function(report) {
|
||||
// Добавляем кастомные кнопки
|
||||
report.page.add_inner_button(__("Export Summary"), function() {
|
||||
export_fuel_summary(report);
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
"after_datatable_render": function(datatable_obj) {
|
||||
// Добавляем итоговую статистику
|
||||
setTimeout(() => {
|
||||
add_fuel_statistics(datatable_obj);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
// Функция экспорта сводки
|
||||
function export_fuel_summary(report) {
|
||||
let data = report.data;
|
||||
let summary = calculate_totals(data);
|
||||
|
||||
// Создаем CSV с итогами
|
||||
let csv_content = "Metric,Aİ-92,Aİ-95,Aİ-80,Dizel yanacağı,Maye qaz,Total\n";
|
||||
csv_content += `Total Quantity,${summary.ai_92},${summary.ai_95},${summary.ai_80},${summary.dizel},${summary.maye_qaz},${summary.total}\n`;
|
||||
csv_content += `Documents Count,${summary.docs_with_ai92},${summary.docs_with_ai95},${summary.docs_with_ai80},${summary.docs_with_dizel},${summary.docs_with_maye_qaz},${summary.total_docs}\n`;
|
||||
|
||||
// Скачиваем файл
|
||||
let blob = new Blob([csv_content], { type: 'text/csv' });
|
||||
let url = window.URL.createObjectURL(blob);
|
||||
let a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'fuel_summary_' + frappe.datetime.get_today() + '.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
frappe.show_alert({message: __("Summary exported successfully"), indicator: "green"});
|
||||
}
|
||||
|
||||
// Функция расчета итогов
|
||||
function calculate_totals(data) {
|
||||
let totals = {
|
||||
ai_92: 0,
|
||||
ai_95: 0,
|
||||
ai_80: 0,
|
||||
dizel: 0,
|
||||
maye_qaz: 0,
|
||||
total: 0,
|
||||
docs_with_ai92: 0,
|
||||
docs_with_ai95: 0,
|
||||
docs_with_ai80: 0,
|
||||
docs_with_dizel: 0,
|
||||
docs_with_maye_qaz: 0,
|
||||
total_docs: data.length
|
||||
};
|
||||
|
||||
data.forEach(row => {
|
||||
totals.ai_92 += row.ai_92 || 0;
|
||||
totals.ai_95 += row.ai_95 || 0;
|
||||
totals.ai_80 += row.ai_80 || 0;
|
||||
totals.dizel += row.dizel || 0;
|
||||
totals.maye_qaz += row.maye_qaz || 0;
|
||||
totals.total += row.total_qty || 0;
|
||||
|
||||
// Считаем документы с каждым типом топлива
|
||||
if (row.ai_92 > 0) totals.docs_with_ai92++;
|
||||
if (row.ai_95 > 0) totals.docs_with_ai95++;
|
||||
if (row.ai_80 > 0) totals.docs_with_ai80++;
|
||||
if (row.dizel > 0) totals.docs_with_dizel++;
|
||||
if (row.maye_qaz > 0) totals.docs_with_maye_qaz++;
|
||||
});
|
||||
|
||||
return totals;
|
||||
}
|
||||
|
||||
// Функция добавления статистики под таблицу
|
||||
function add_fuel_statistics(datatable_obj) {
|
||||
if (datatable_obj && datatable_obj.data) {
|
||||
let totals = calculate_totals(datatable_obj.data);
|
||||
|
||||
let stats_html = '<div class="fuel-statistics" style="margin-top: 20px; padding: 20px; background: #f8f9fa; border-radius: 8px; border: 1px solid #dee2e6;">';
|
||||
stats_html += '<h4 style="margin-bottom: 15px; color: #495057;">📈 Итоговая статистика по периоду:</h4>';
|
||||
|
||||
stats_html += '<div class="row">';
|
||||
stats_html += '<div class="col-md-6">';
|
||||
stats_html += '<h5>Общее количество по типам:</h5>';
|
||||
stats_html += `<div style="margin-bottom: 8px;"><span style="color: #28a745;">●</span> Aİ-92: <strong>${totals.ai_92.toFixed(2)}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;"><span style="color: #007bff;">●</span> Aİ-95: <strong>${totals.ai_95.toFixed(2)}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;"><span style="color: #6c757d;">●</span> Aİ-80: <strong>${totals.ai_80.toFixed(2)}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;"><span style="color: #fd7e14;">●</span> Dizel: <strong>${totals.dizel.toFixed(2)}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;"><span style="color: #e83e8c;">●</span> Maye qaz: <strong>${totals.maye_qaz.toFixed(2)}</strong></div>`;
|
||||
stats_html += '</div>';
|
||||
|
||||
stats_html += '<div class="col-md-6">';
|
||||
stats_html += '<h5>Количество документов:</h5>';
|
||||
stats_html += `<div style="margin-bottom: 8px;">Всего документов: <strong>${totals.total_docs}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;">С Aİ-92: <strong>${totals.docs_with_ai92}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;">С Aİ-95: <strong>${totals.docs_with_ai95}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;">С Aİ-80: <strong>${totals.docs_with_ai80}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;">С Dizel: <strong>${totals.docs_with_dizel}</strong></div>`;
|
||||
stats_html += `<div style="margin-bottom: 8px;">С Maye qaz: <strong>${totals.docs_with_maye_qaz}</strong></div>`;
|
||||
stats_html += '</div>';
|
||||
stats_html += '</div>';
|
||||
|
||||
stats_html += `<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #dee2e6;">`;
|
||||
stats_html += `<strong>🔥 Общее количество топлива: ${totals.total.toFixed(2)}</strong>`;
|
||||
stats_html += `</div>`;
|
||||
|
||||
stats_html += '</div>';
|
||||
|
||||
// Удаляем предыдущую статистику если есть
|
||||
$('.fuel-statistics').remove();
|
||||
|
||||
// Добавляем новую статистику
|
||||
$(stats_html).insertAfter('.datatable');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2025-08-15 17:22:57.350629",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-08-15 17:22:57.350629",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Taxes Az",
|
||||
"name": "Fuel Products Purchase Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Fuel Products Purchase Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"timeout": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
# Copyright (c) 2024, Your Company and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
"label": _("Purchase Invoice"),
|
||||
"fieldname": "invoice",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Invoice",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Date"),
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Supplier"),
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Aİ-92"),
|
||||
"fieldname": "ai_92",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
},
|
||||
{
|
||||
"label": _("Aİ-95"),
|
||||
"fieldname": "ai_95",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
},
|
||||
{
|
||||
"label": _("Aİ-80"),
|
||||
"fieldname": "ai_80",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
},
|
||||
{
|
||||
"label": _("Dizel yanacağı"),
|
||||
"fieldname": "dizel",
|
||||
"fieldtype": "Float",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Maye qaz"),
|
||||
"fieldname": "maye_qaz",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
},
|
||||
{
|
||||
"label": _("Total Qty"),
|
||||
"fieldname": "total_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 100
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
# Хардкод фильтр на топливные продукты
|
||||
fuel_items = [
|
||||
"Avtomobil benzini (Aİ-92)",
|
||||
"Avtomobil benzini (Aİ-95)",
|
||||
"Avtomobil benzini (Aİ-80)",
|
||||
"Dizel yanacağı",
|
||||
"Maye qaz"
|
||||
]
|
||||
|
||||
conditions = []
|
||||
values = []
|
||||
|
||||
# Фильтр по датам
|
||||
if filters.get("from_date"):
|
||||
conditions.append("pi.posting_date >= %s")
|
||||
values.append(filters.get("from_date"))
|
||||
|
||||
if filters.get("to_date"):
|
||||
conditions.append("pi.posting_date <= %s")
|
||||
values.append(filters.get("to_date"))
|
||||
|
||||
# Хардкод фильтр по товарам
|
||||
item_condition = "pii.item_name IN ({})".format(", ".join(["%s"] * len(fuel_items)))
|
||||
conditions.append(item_condition)
|
||||
values.extend(fuel_items)
|
||||
|
||||
# Только подтвержденные инвойсы
|
||||
conditions.append("pi.docstatus = 1")
|
||||
|
||||
where_clause = ""
|
||||
if conditions:
|
||||
where_clause = "WHERE " + " AND ".join(conditions)
|
||||
|
||||
# Получаем все данные
|
||||
query = """
|
||||
SELECT
|
||||
pi.name as invoice,
|
||||
pi.posting_date,
|
||||
pi.supplier,
|
||||
pii.item_name,
|
||||
pii.qty
|
||||
FROM
|
||||
`tabPurchase Invoice Item` pii
|
||||
INNER JOIN
|
||||
`tabPurchase Invoice` pi ON pii.parent = pi.name
|
||||
{where_clause}
|
||||
ORDER BY
|
||||
pi.posting_date DESC, pi.name
|
||||
""".format(where_clause=where_clause)
|
||||
|
||||
raw_data = frappe.db.sql(query, values, as_dict=True)
|
||||
|
||||
# Группируем данные по документам
|
||||
invoice_data = {}
|
||||
|
||||
for row in raw_data:
|
||||
invoice = row['invoice']
|
||||
|
||||
if invoice not in invoice_data:
|
||||
invoice_data[invoice] = {
|
||||
'invoice': invoice,
|
||||
'posting_date': row['posting_date'],
|
||||
'supplier': row['supplier'],
|
||||
'ai_92': 0,
|
||||
'ai_95': 0,
|
||||
'ai_80': 0,
|
||||
'dizel': 0,
|
||||
'maye_qaz': 0,
|
||||
'total_qty': 0
|
||||
}
|
||||
|
||||
# Распределяем количество по колонкам
|
||||
item_name = row['item_name']
|
||||
qty = row['qty'] or 0
|
||||
|
||||
if 'Aİ-92' in item_name:
|
||||
invoice_data[invoice]['ai_92'] += qty
|
||||
elif 'Aİ-95' in item_name:
|
||||
invoice_data[invoice]['ai_95'] += qty
|
||||
elif 'Aİ-80' in item_name:
|
||||
invoice_data[invoice]['ai_80'] += qty
|
||||
elif 'Dizel' in item_name:
|
||||
invoice_data[invoice]['dizel'] += qty
|
||||
elif 'Maye qaz' in item_name:
|
||||
invoice_data[invoice]['maye_qaz'] += qty
|
||||
|
||||
invoice_data[invoice]['total_qty'] += qty
|
||||
|
||||
# Конвертируем в список
|
||||
data = list(invoice_data.values())
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_filters():
|
||||
return [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": _("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.utils.add_months(frappe.utils.today(), -1),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": _("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.utils.today(),
|
||||
"reqd": 1
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue