انت مبرمج محترف في البرمجة باستخدام frappe framework ولغة البرمجة paython ولغة البرمجة JavaScrept, وانا اريد مساعدتك , في انشاء تقرير جديد والذي سوف ارسل لك اسم التقرير وتفاصليله


مرحبًا! بالطبع، يمكنني مساعدتك في إنشاء تقرير جديد باستخدام Frappe Framework ولغتي البرمجة Python وJavaScript. يمكنك إرسال اسم التقرير وتفاصيله، وسأكون سعيدًا بمساعدتك في ذلك.


Report: Total Item Cost + Quantity Sold in a Specific Period
Report Description:
A report that displays the total cost and quantity sold for each item within a specific date range. This report helps analyse item performance and profitability.

Report Fields:
[
{"fieldname": "item_code", "label": "Item Code", "fieldtype": "Link", "options": "Item", "width": 150},
{"fieldname": "item_name", "label": "Item Name", "fieldtype": "Data", "width": 200},
{"fieldname": "quantity_sold", "label": "Quantity Sold", "fieldtype": "Float", "width": 100},
{"fieldname": "valuation_rate", "label": "Valuation Rate", "fieldtype": "Currency", "width": 150},
{"fieldname": "total_revenue", "label": "Total Revenue", "fieldtype": "Currency", "width": 150},
{"fieldname": "gross_profit", "label": "Gross Profit", "fieldtype": "Currency", "width": 150},
]

Filters:
frappe.query_reports["Total Item Cost + Quantity Sold"] = {
"filters": [
{
"fieldname": "start_date",
"label": __("Start Date"),
"fieldtype": "Date",
"reqd": 1,
},
{
"fieldname": "end_date",
"label": __("End Date"),
"fieldtype": "Date",
"reqd": 1,
},
{
"fieldname": "item_code",
"label": __("Item Code"),
"fieldtype": "Link",
"options": "Item",
"default": "",
"reqd": 0
},
{
"fieldname": "item_group",
"label": __("Item Group"),
"fieldtype": "Link",
"options": "Item Group",
"default": "",
"reqd": 0
},
{
"fieldname": "show_zero_sales",
"label": __("Show Items with Zero Sales"),
"fieldtype": "Check",
"default": 0
}
]
};

Calculations:
Total Cost = Quantity Sold × Valuation Rate
Total Revenue = Quantity Sold × Selling Price
Gross Profit = Total Revenue - Total Cost

Report Format:
It should contain icons for each column
Should be sorted in DESCENDING ORDER
Show the total Quantity sold for Each item for a specific DATE RANGE

Implementation in Frappe
Report Name: Total Item Cost + Quantity Sold
Report Type: Script Report
Doctype: Sales Invoice


إليك خطوات لإنشاء التقرير الذي طلبته باستخدام Frappe Framework. سنقوم بإنشاء تقرير جديد باسم "Total Item Cost + Quantity Sold" مع استخدام البيانات المحددة في طلبك.

الخطوة 1: إنشاء ملف جافا سكريبت للتقرير
في مجلد تطبيقك، انتقل إلى frappe/public/js وأنشئ ملفًا جديدًا باسم total_item_cost_quantity_sold.js.


frappe.query_reports["Total Item Cost + Quantity Sold"] = {
    "filters": [
        {
            "fieldname": "start_date",
            "label": __("Start Date"),
            "fieldtype": "Date",
            "reqd": 1,
        },
        {
            "fieldname": "end_date",
            "label": __("End Date"),
            "fieldtype": "Date",
            "reqd": 1,
        },
        {
            "fieldname": "item_code",
            "label": __("Item Code"),
            "fieldtype": "Link",
            "options": "Item",
            "default": "",
            "reqd": 0
        },
        {
            "fieldname": "item_group",
            "label": __("Item Group"),
            "fieldtype": "Link",
            "options": "Item Group",
            "default": "",
            "reqd": 0
        },
        {
            "fieldname": "show_zero_sales",
            "label": __("Show Items with Zero Sales"),
            "fieldtype": "Check",
            "default": 0
        }
    ],
    onload: function(report) {
        // Custom logic on report load
    },
};
الخطوة 2: إنشاء ملف Python للتقرير
انتقل إلى مجلد تطبيقك وداخل مجلد report، قم بإنشاء ملف Python يسمى total_item_cost_quantity_sold.py.


import frappe
from frappe import _

@frappe.whitelist()
def execute(filters=None):
    columns = [
        {"fieldname": "item_code", "label": _("Item Code"), "fieldtype": "Link", "options": "Item", "width": 150},
        {"fieldname": "item_name", "label": _("Item Name"), "fieldtype": "Data", "width": 200},
        {"fieldname": "quantity_sold", "label": _("Quantity Sold"), "fieldtype": "Float", "width": 100},
        {"fieldname": "valuation_rate", "label": _("Valuation Rate"), "fieldtype": "Currency", "width": 150},
        {"fieldname": "total_revenue", "label": _("Total Revenue"), "fieldtype": "Currency", "width": 150},
        {"fieldname": "gross_profit", "label": _("Gross Profit"), "fieldtype": "Currency", "width": 150},
    ]
    
    data = []
    
    # Get filters
    start_date = filters.get('start_date')
    end_date = filters.get('end_date')
    item_code = filters.get('item_code')
    item_group = filters.get('item_group')
    show_zero_sales = filters.get('show_zero_sales')

    # Query to get sales order data
    conditions = []
    if start_date:
        conditions.append(f"si.posting_date >= '{start_date}'")
    if end_date:
        conditions.append(f"si.posting_date <= '{end_date}'")
    if item_code:
        conditions.append(f"si_item.item_code = '{item_code}'")
    if item_group:
        conditions.append(f"i.item_group = '{item_group}'")

    condition_str = " AND ".join(conditions)

    query = f"""
        SELECT
            si_item.item_code,
            si_item.item_name,
            SUM(si_item.qty) AS quantity_sold,
            AVG(si_item.valuation_rate) AS valuation_rate,
            SUM(si_item.amount) AS total_revenue
        FROM
            `tabSales Invoice Item` si_item
        JOIN
            `tabSales Invoice` si ON si.name = si_item.parent
        JOIN
            `tabItem` i ON i.name = si_item.item_code
        WHERE
            {condition_str}
        GROUP BY
            si_item.item_code
        ORDER BY
            quantity_sold DESC
    """

    # Execute query
    results = frappe.db.sql(query, as_dict=True)

    for row in results:
        total_cost = row.quantity_sold * row.valuation_rate
        gross_profit = row.total_revenue - total_cost

        data.append({
            "item_code": row.item_code,
            "item_name": row.item_name,
            "quantity_sold": row.quantity_sold,
            "valuation_rate": row.valuation_rate,
            "total_revenue": row.total_revenue,
            "gross_profit": gross_profit,
        })

    return columns, data
الخطوة 3: تسجيل التقرير في الخادم
بعد إنشاء الملفات، يجب تسجيل التقرير في Frappe. افتح ملف hooks.py في تطبيقك وأضف ما يلي:


doc_events = {
    "Sales Invoice": {
        "after_insert": "your_app_name.report.total_item_cost_quantity_sold.execute",
    }
}
تأكد من استخدام اسم تطبيقك الصحيح في your_app_name.

الخطوة 4: اعادة تشغيل الخادم
بعد إجراء التغييرات، تأكد من إعادة تشغيل خادم Frappe لتفعيل أي تعديلات.

الخطوة 5: اختبار التقرير
الآن يمكنك الذهاب إلى واجهة Frappe وتجربة التقرير الجديد من قائمة التقارير. أدخل النطاق الزمني الذي تريد اختبار التقرير خلاله.

إذا كان لديك أي استفسارات أو تحتاج إلى تعديلات إضافية، فلا تتردد في طرح الأسئلة!


App Versions

{
        "ebkar_reports": "1.0.0",
        "erpnext": "15.33.0",
        "frappe": "15.48.0",
        "hrms": "15.27.0",
        "time_sheet_tool": "0.0.3"
}
Route

query-report/Total Item Cost + Quantity Sold
Traceback

Traceback (most recent call last):
  File "apps/frappe/frappe/app.py", line 114, in application
    response = frappe.api.handle(request)
  File "apps/frappe/frappe/api/__init__.py", line 49, in handle
    data = endpoint(**arguments)
  File "apps/frappe/frappe/api/v1.py", line 36, in handle_rpc_call
    return frappe.handler.handle()
  File "apps/frappe/frappe/handler.py", line 49, in handle
    data = execute_cmd(cmd)
  File "apps/frappe/frappe/handler.py", line 85, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "apps/frappe/frappe/__init__.py", line 1725, in call
    return fn(*args, **newargs)
  File "apps/frappe/frappe/utils/typing_validations.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "apps/frappe/frappe/__init__.py", line 878, in wrapper_fn
    retval = fn(*args, **get_newargs(fn, kwargs))
  File "apps/frappe/frappe/desk/query_report.py", line 224, in run
    result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field)
  File "apps/frappe/frappe/__init__.py", line 878, in wrapper_fn
    retval = fn(*args, **get_newargs(fn, kwargs))
  File "apps/frappe/frappe/desk/query_report.py", line 84, in generate_report_result
    res = get_report_result(report, filters) or []
  File "apps/frappe/frappe/desk/query_report.py", line 65, in get_report_result
    res = report.execute_script_report(filters)
  File "apps/frappe/frappe/core/doctype/report/report.py", line 163, in execute_script_report
    res = self.execute_module(filters)
  File "apps/frappe/frappe/core/doctype/report/report.py", line 180, in execute_module
    return frappe.get_attr(method_name)(frappe._dict(filters))
  File "apps/frappe/frappe/utils/typing_validations.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "apps/ebkar_reports/ebkar_reports/ebkar_reports/report/total_item_cost_+_quantity_sold/total_item_cost_+_quantity_sold.py", line 62, in execute
    results = frappe.db.sql(query, as_dict=True)
  File "apps/frappe/frappe/database/database.py", line 229, in sql
    self._cursor.execute(query, values)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 153, in execute
    result = self._query(query)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 322, in _query
    conn.query(q)
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 563, in query
    self._affected_rows = self._read_query_result(unbuffered=unbuffered)
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 825, in _read_query_result
    result.read()
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 1199, in read
    first_packet = self.connection._read_packet()
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 775, in _read_packet
    packet.raise_for_error()
  File "env/lib/python3.10/site-packages/pymysql/protocol.py", line 219, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "env/lib/python3.10/site-packages/pymysql/err.py", line 150, in raise_mysql_exception
    raise errorclass(errno, errval)
pymysql.err.OperationalError: (1054, "Unknown column 'si_item.valuation_rate' in 'field list'")
Request Data

{
        "type": "GET",
        "args": {
                "report_name": "Total Item Cost + Quantity Sold",
                "filters": "{\"start_date\":\"2024-12-01\",\"end_date\":\"2024-12-02\"}",
                "ignore_prepared_report": false,
                "are_default_filters": false
        },
        "headers": {},
        "error_handlers": {},
        "url": "/api/method/frappe.desk.query_report.run",
        "request_id": null
}
Response Data

{
        "exception": "pymysql.err.OperationalError: (1054, \"Unknown column 'si_item.valuation_rate' in 'field list'\")",
        "exc_type": "OperationalError",
        "_exc_source": "ebkar_reports (app)"
}

المشكلة التي تواجهها تتعلق بخطأ SQL حيث يشير الخطأ إلى عدم وجود العمود valuation_rate في جدول Sales Invoice Item (الذي يُشار إليه بـ si_item في الاستعلام).

توضيح المشكلة
في نموذج Sales Invoice Item في Frappe/ERPNext، قد لا يكون لديك عمود يسمى valuation_rate. بدلاً من ذلك، قد تحتاج إلى حساب سعر التقييم بناءً على جدول آخر أو قيمة معينة مرتبطة بالمنتج.

خطوات الحل
تأكيد اسم العمود: تأكد من وجود العمود valuation_rate في جدول tabSales Invoice Item. يمكنك استخدام أداة SQL أو استعراض الهيكل في Frappe للتأكد من أسماء الأعمدة المتاحة.
تعديل الاستعلام: إذا كان عمود valuation_rate غير موجود، يمكنك حسابه من جدول tabItem، حيث يوجد عادةً عمود يُشير إلى تكلفة التقييم (مثل standard_rate أو valuation_rate). يمكنك تعديل الاستعلام الخاص بك على النحو التالي:

query = f"""
    SELECT
        si_item.item_code,
        si_item.item_name,
        SUM(si_item.qty) AS quantity_sold,
        AVG(i.standard_rate) AS valuation_rate,  -- استبدل `valuation_rate` بعمود مناسب
        SUM(si_item.amount) AS total_revenue
    FROM
        `tabSales Invoice Item` si_item
    JOIN
        `tabSales Invoice` si ON si.name = si_item.parent
    JOIN
        `tabItem` i ON i.name = si_item.item_code
    WHERE
        {condition_str}
    GROUP BY
        si_item.item_code
    ORDER BY
        quantity_sold DESC
"""
3. توافر بيانات فواتير البيع: تأكد من وجود بيانات في النظام لسجل Sales Invoice وSales Invoice Item للفترة المحددة في الفلتر.
4. إعادة تشغيل و اختبار: بعد تعديل الاستعلام، تأكد من إعادة تشغيل خادم Frappe واختبر التقرير مرة أخرى.
ملاحظة
إذا كنت بحاجة إلى حساب شيء معين مثل valuation_rate من مصادر أخرى أو حينما تكون لديك علاقة معقدة بين الجداول، قد تحتاج إلى إنشاء استعلام أكثر تعقيدًا أو حتى وظيفة مخصصة لحساب القيم.

إذا استمرت المشكلة، يرجى تقديم اسم الأعمدة الموجودة في جدول tabSales Invoice Item لمزيد من المساعدة.


valuation_rate متواجد في Doctype ال Item


إذا كان لديك بالفعل العمود valuation_rate في نوع المستند (Doctype) "Item"، فستحتاج إلى استخدام هذا العمود في الاستعلام الخاص بك عبر جدول tabSales Invoice Item وtabItem دون أي مشاكل.

ومع ذلك، استنادًا إلى الخطأ الذي يظهر، يبدو أن اسم العمود غير صحيح أو أنه يوجد اتصال خاطئ بين الجداول. لنقم بتعديل الاستعلام لمعالجة ذلك:

تعديل استعلام SQL
يمكنك استخدام العمود valuation_rate من جدول tabItem مباشرةً. هنا هو تعديل الاستعلام لإضافة اسم العمود الصحيح:


# تعديل الاستعلام لاستخدام valuation_rate من جدول Item
query = f"""
    SELECT
        si_item.item_code,
        i.item_name,
        SUM(si_item.qty) AS quantity_sold,
        i.valuation_rate AS valuation_rate,  -- استخدام valuation_rate من جدول Items
        SUM(si_item.amount) AS total_revenue
    FROM
        `tabSales Invoice Item` si_item
    JOIN
        `tabSales Invoice` si ON si.name = si_item.parent
    JOIN
        `tabItem` i ON i.name = si_item.item_code
    WHERE
        {condition_str}
    GROUP BY
        si_item.item_code
    ORDER BY
        quantity_sold DESC
"""
ملاحظات حول الاستعلام:
تأكد من أن لديك البيانات المناسبة في الجداول tabSales Invoice وtabSales Invoice Item.
تأكد من أن عمود item_code في tabSales Invoice Item يتطابق مع العمود name في tabItem.
يمكن استخدام i.item_name مباشرةً في الاستعلام بدلاً من الحصول عليه من si_item.
تنفيذ واستعادة
تحديث الكود: أضف التعديل أعلاه إلى ملف Python الخاص بالتقرير.
إعادة تشغيل خادم Frappe: بعد إجراء التعديلات، قم بإعادة تشغيل خادم Frappe.
اختبار التقرير: حاول تشغيل التقرير مرة أخرى وتحقق مما إذا كانت المشكلة لا تزال قائمة.
إذا كنت تواجه أي مشكلات إضافية أو تحتاج إلى مزيد من المساعدة، فلا تتردد في سؤالي!