Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions aqrar_ext/api/branch_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# aqrar_ext: Get branch defaults for current user
import frappe


@frappe.whitelist()
def get_user_branch_defaults():
"""Return warehouse and cost_center from user's Branch Configuration."""
user = frappe.session.user
result = frappe.db.sql("""
SELECT bcw.warehouse, bcc.cost_center
FROM `tabBranch Configuration` bc
INNER JOIN `tabBranch Configuration User` bcu ON bcu.parent = bc.name
LEFT JOIN `tabBranch Configuration Warehouse` bcw ON bcw.parent = bc.name
LEFT JOIN `tabBranch Configuration Cost Center` bcc ON bcc.parent = bc.name
WHERE bcu.user = %s
LIMIT 1
""", user, as_dict=True)

if result:
return result[0]
return {}
Empty file.
34 changes: 34 additions & 0 deletions aqrar_ext/aqrar_ext/overrides/stock_ledger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# aqrar_ext: Override Stock Ledger to support voucher_type filter + single item
from erpnext.stock.report.stock_ledger import stock_ledger

_original_get_sle = stock_ledger.get_stock_ledger_entries
_original_get_items = stock_ledger.get_items

PURCHASE_TYPES = ("Purchase Invoice", "Purchase Receipt")
SALE_TYPES = ("Sales Invoice", "Delivery Note")


def _patched_get_sle(filters, items):
result = _original_get_sle(filters, items)
voucher_type = filters.get("voucher_type")
if voucher_type and voucher_type != "All":
if voucher_type == "Purchase Only":
result = [r for r in result if r.voucher_type in PURCHASE_TYPES]
elif voucher_type == "Sale Only":
result = [r for r in result if r.voucher_type in SALE_TYPES]
elif voucher_type in ("Transfer Only", "Stock Entry Only"):
result = [r for r in result if r.voucher_type == "Stock Entry"]
return result


def _patched_get_items(filters):
# Convert single item_code string to list for compatibility
item_code = filters.get("item_code")
if item_code and isinstance(item_code, str):
filters = dict(filters)
filters["item_code"] = [item_code]
return _original_get_items(filters)


stock_ledger.get_stock_ledger_entries = _patched_get_sle
stock_ledger.get_items = _patched_get_items
36 changes: 36 additions & 0 deletions aqrar_ext/fixtures/print_format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[
{
"absolute_value": 0,
"align_labels_right": 0,
"css": null,
"custom_format": 1,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Delivery Note",
"docstatus": 0,
"doctype": "Print Format",
"font": null,
"font_size": 14,
"format_data": null,
"html": "<style>\n .dn-wrap { font-family: Arial, sans-serif; font-size: 13px; color: #000; padding: 20px; }\n .dn-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }\n .dn-table th { background: #f0f0f0; border: 1px solid #ccc; padding: 7px 10px; text-align: left; font-size: 12px; font-weight: 700; }\n .dn-table td { border: 1px solid #ddd; padding: 6px 10px; font-size: 12px; vertical-align: middle; }\n</style>\n\n<div class=\"dn-wrap\">\n\n <!-- Title -->\n <div style=\"text-align:center; margin-bottom:16px;\">\n <h2 style=\"font-size:22px;font-weight:700;margin:0 0 4px 0;\">Delivery Note</h2>\n <p style=\"margin:2px 0;font-size:12px;color:#555;\">{{ doc.name }}</p>\n {% if doc.docstatus == 0 %}\n <p style=\"color:red;font-weight:700;font-size:13px;margin:4px 0;\">DRAFT</p>\n {% endif %}\n </div>\n\n <!-- Customer & Date -->\n <div style=\"display:flex;justify-content:space-between;margin-bottom:16px;font-size:12px;\">\n <div style=\"line-height:2;\">\n <b>Customer Name:</b><br>\n <b>{{ doc.customer_name or doc.customer }}</b>\n </div>\n <div style=\"text-align:right;line-height:2;\">\n <b>Date:</b> {{ doc.posting_date }}<br>\n <b>Company:</b> {{ doc.company }}\n </div>\n </div>\n\n <!-- Items Table -->\n <table class=\"dn-table\">\n <thead>\n <tr>\n <th style=\"width:40px;\">Sr</th>\n <th>Item Code</th>\n <th>Description</th>\n <th style=\"text-align:center;width:80px;\">Quantity</th>\n <th style=\"text-align:center;width:60px;\">UOM</th>\n </tr>\n </thead>\n <tbody>\n {% for item in doc.items %}\n <tr>\n <td>{{ loop.index }}</td>\n <td>{{ item.item_code }}<br>\n <small style=\"color:#555;\">{{ item.item_name }}</small>\n </td>\n <td>{{ item.description or \"\" }}</td>\n <td style=\"text-align:center;\">{{ item.qty }}</td>\n <td style=\"text-align:center;\">{{ item.uom }}</td>\n </tr>\n {% endfor %}\n </tbody>\n <tfoot>\n <tr>\n <td colspan=\"3\" style=\"text-align:right;font-weight:700;padding:6px 10px;border:1px solid #ddd;\">\n Total Quantity:\n </td>\n <td style=\"text-align:center;font-weight:700;padding:6px 10px;border:1px solid #ddd;\">\n {{ doc.total_qty }}\n </td>\n <td style=\"border:1px solid #ddd;\"></td>\n </tr>\n </tfoot>\n </table>\n\n <!-- Terms -->\n {% if doc.terms %}\n <div style=\"margin-bottom:16px;font-size:12px;\">\n <b>Terms & Conditions:</b><br>{{ doc.terms }}\n </div>\n {% endif %}\n\n <!-- Signature -->\n <div style=\"margin-top:50px;display:flex;justify-content:space-between;font-size:12px;text-align:center;\">\n <div>\n <div style=\"border-top:1px solid #000;width:160px;margin:40px auto 4px auto;\"></div>\n Prepared By\n </div>\n <div>\n <div style=\"border-top:1px solid #000;width:160px;margin:40px auto 4px auto;\"></div>\n Authorized Signatory\n </div>\n <div>\n <div style=\"border-top:1px solid #000;width:160px;margin:40px auto 4px auto;\"></div>\n Received By\n </div>\n </div>\n\n</div>",
"line_breaks": 0,
"margin_bottom": 15.0,
"margin_left": 15.0,
"margin_right": 15.0,
"margin_top": 15.0,
"modified": "2026-05-16 22:57:41.237619",
"module": "Stock",
"name": "Aqrar Delivery Note",
"page_number": "Hide",
"pdf_generator": "wkhtmltopdf",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_for": "DocType",
"print_format_type": "Jinja",
"raw_commands": null,
"raw_printing": 0,
"report": null,
"show_section_headings": 0,
"standard": "Yes"
}
]
135 changes: 135 additions & 0 deletions aqrar_ext/fixtures/workflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
[
{
"docstatus": 0,
"doctype": "Workflow",
"document_type": "Material Request",
"is_active": 1,
"modified": "2026-05-19 16:15:59.237356",
"name": "Material Request Approval",
"override_status": 0,
"send_email_alert": 0,
"states": [
{
"allow_edit": "Branch User",
"avoid_status_override": 0,
"doc_status": "1",
"is_optional_state": 0,
"message": "Waiting for Branch User approval",
"next_action_email_template": null,
"parent": "Material Request Approval",
"parentfield": "states",
"parenttype": "Workflow",
"send_email": 0,
"state": "Pending Approval",
"update_field": "workflow_state",
"update_value": null,
"workflow_builder_id": null
},
{
"allow_edit": "Stock Manager",
"avoid_status_override": 0,
"doc_status": "1",
"is_optional_state": 0,
"message": "Approved - Ready for Transfer",
"next_action_email_template": null,
"parent": "Material Request Approval",
"parentfield": "states",
"parenttype": "Workflow",
"send_email": 0,
"state": "Approved",
"update_field": "workflow_state",
"update_value": null,
"workflow_builder_id": null
},
{
"allow_edit": "Stock Manager",
"avoid_status_override": 0,
"doc_status": "1",
"is_optional_state": 0,
"message": "Fully Transferred",
"next_action_email_template": null,
"parent": "Material Request Approval",
"parentfield": "states",
"parenttype": "Workflow",
"send_email": 0,
"state": "Transferred",
"update_field": "workflow_state",
"update_value": null,
"workflow_builder_id": null
},
{
"allow_edit": "Branch User",
"avoid_status_override": 0,
"doc_status": "1",
"is_optional_state": 0,
"message": "Closed - Not Fully Fulfilled",
"next_action_email_template": null,
"parent": "Material Request Approval",
"parentfield": "states",
"parenttype": "Workflow",
"send_email": 0,
"state": "Closed",
"update_field": "workflow_state",
"update_value": null,
"workflow_builder_id": null
}
],
"transitions": [
{
"action": "Approve",
"allow_self_approval": 0,
"allowed": "Branch User",
"condition": "frappe.session.user != doc.owner",
"next_state": "Approved",
"parent": "Material Request Approval",
"parentfield": "transitions",
"parenttype": "Workflow",
"send_email_to_creator": 0,
"state": "Pending Approval",
"workflow_builder_id": null
},
{
"action": "Reject",
"allow_self_approval": 0,
"allowed": "Branch User",
"condition": null,
"next_state": "Closed",
"parent": "Material Request Approval",
"parentfield": "transitions",
"parenttype": "Workflow",
"send_email_to_creator": 0,
"state": "Pending Approval",
"workflow_builder_id": null
},
{
"action": "Cancel",
"allow_self_approval": 0,
"allowed": "Branch User",
"condition": null,
"next_state": "Closed",
"parent": "Material Request Approval",
"parentfield": "transitions",
"parenttype": "Workflow",
"send_email_to_creator": 0,
"state": "Approved",
"workflow_builder_id": null
},
{
"action": "Transfer",
"allow_self_approval": 1,
"allowed": "Stock Manager",
"condition": "frappe.db.get_value('Material Request', doc.name, 'per_ordered') == 100",
"next_state": "Transferred",
"parent": "Material Request Approval",
"parentfield": "transitions",
"parenttype": "Workflow",
"send_email_to_creator": 0,
"state": "Approved",
"workflow_builder_id": null
}
],
"workflow_data": null,
"workflow_name": "Material Request Approval",
"workflow_state_field": "workflow_state"
}
]
31 changes: 31 additions & 0 deletions aqrar_ext/patches/fix_stock_entry_naming_series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Fix duplicate Stock Transfer naming by syncing naming series counter
import frappe
from datetime import date


def execute():
current_year = date.today().year
series_name = f"MAT-STE-{current_year}-"

current = frappe.db.sql(
"SELECT current FROM `tabSeries` WHERE name = %s",
(series_name,)
)
current_val = current[0][0] if current else 0

max_entry = frappe.db.sql(
"SELECT name FROM `tabStock Entry` WHERE name LIKE %s ORDER BY name DESC LIMIT 1",
(f"MAT-STE-{current_year}-%",)
)

if not max_entry:
return

actual_max = int(max_entry[0][0].split("-")[-1])

if actual_max >= (current_val or 0):
frappe.db.sql(
"UPDATE `tabSeries` SET current = %s WHERE name = %s",
(actual_max + 1, series_name)
)
frappe.db.commit()
91 changes: 91 additions & 0 deletions aqrar_ext/public/js/material_request_custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// aqrar_ext: Material Request — running counter + close button + urgent
frappe.ui.form.on("Material Request", {
refresh(frm) {
show_fulfillment(frm);

if (frm.doc.docstatus === 0) return;

// Close button (submitted, not closed, not 100% done)
if (frm.doc.docstatus === 1 && !frm.doc.custom_close_reason
&& frm.doc.per_ordered < 100) {
frm.add_custom_button(__("Close MR"), function () {
show_close_dialog(frm);
}, __("Actions"));
}

// Reopen button
if (frm.doc.docstatus === 1 && frm.doc.custom_close_reason) {
frm.add_custom_button(__("Reopen MR"), function () {
frappe.confirm(__("Reopen this Material Request?"), function () {
frappe.call({
method: "aqrar_ext.events.material_request.reopen_material_request",
args: { mr_name: frm.doc.name },
freeze: true,
callback: function () { frm.reload_doc(); },
});
});
}, __("Actions"));
}
},

before_save(frm) {
if (frm.doc.custom_close_reason && frm.doc.docstatus === 1) {
frm.set_value("status", "Stopped");
}
},
});

function show_fulfillment(frm) {
// Remove previous
$(".aqrar-fulfillment").remove();

var total_req = 0, total_done = 0;
(frm.doc.items || []).forEach(function (item) {
total_req += flt(item.stock_qty || item.qty || 0);
total_done += flt(item.ordered_qty || 0);
});

var pct = total_req > 0 ? Math.round((total_done / total_req) * 100) : 0;
var color = pct >= 100 ? "green" : pct > 0 ? "orange" : "gray";
var bar = "";
for (var i = 0; i < 10; i++) bar += i < Math.round(pct / 10) ? "█" : "░";

var html = '<div class="aqrar-fulfillment" style="padding:10px 15px;background:#f5f7fa;' +
'border-radius:6px;margin-bottom:10px;font-size:13px;">' +
'<strong>' + __("Fulfillment") + ':</strong> ' +
'<span style="color:' + (pct >= 100 ? '#16a34a' : pct > 0 ? '#d97706' : '#6b7280') + ';font-weight:bold;">' +
pct + '%</span> ' +
'(' + total_done + ' / ' + total_req + ' ' + __("transferred") + ') ' +
'<span style="font-family:monospace;">' + bar + '</span></div>';

var $ctrl = $(frm.fields_dict.items.wrapper);
$ctrl.prepend(html);
}

function show_close_dialog(frm) {
var d = new frappe.ui.Dialog({
title: __("Close Material Request"),
fields: [
{
fieldtype: "Small Text",
fieldname: "reason",
label: __("Reason for Closing"),
reqd: 1,
},
],
primary_action_label: __("Close MR"),
primary_action(values) {
d.hide();
frappe.call({
method: "aqrar_ext.events.material_request.close_material_request",
args: {
mr_name: frm.doc.name,
reason: values.reason,
},
freeze: true,
callback: function () { frm.reload_doc(); },
});
},
});
d.show();
}
Loading
Loading