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
30 changes: 30 additions & 0 deletions ram_custom/api/address_arabic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import frappe

# (visible field, older field that print formats still read)
ARABIC_FIELD_PAIRS = (
("custom_address_line1_arabic", "custom_address_line_1_arabic"),
("custom_area_arabic", "custom_areadistrict_arabic"),
("custom_city_arabic", "custom_citytown_arabic"),
)


def sync_arabic_fields(doc, method=None):
"""Keep the two parallel sets of Arabic address fields identical.

The form only shows the first field of each pair; print formats written
earlier still read the second. Whatever the user (or the translate button /
auto-translation) puts in the visible field is mirrored into the older one.
If only the older field has text (legacy rows), it is copied up so nothing
is lost on the next save.
"""
for visible, older in ARABIC_FIELD_PAIRS:
if not (doc.meta.has_field(visible) and doc.meta.has_field(older)):
continue

visible_value = (doc.get(visible) or "").strip()
older_value = (doc.get(older) or "").strip()

if visible_value:
doc.set(older, visible_value)
elif older_value:
doc.set(visible, older_value)
28 changes: 28 additions & 0 deletions ram_custom/fixtures/print_format.json

Large diffs are not rendered by default.

52 changes: 50 additions & 2 deletions ram_custom/fixtures/property_setter.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@
"doctype_or_field": "DocField",
"field_name": "phone",
"is_system_generated": 1,
"modified": "2026-09-15 09:04:49.229767",
"modified": "2026-10-13 12:00:00.000000",
"module": "Ram Custom",
"name": "Address-phone-hidden",
"property": "hidden",
"property_type": "Check",
"row_name": null,
"value": "1"
"value": "0"
},
{
"default_value": null,
Expand Down Expand Up @@ -254,5 +254,53 @@
"property_type": "Check",
"row_name": null,
"value": "1"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "custom_address_line_1_arabic",
"is_system_generated": 1,
"modified": "2026-10-13 12:00:00.000000",
"module": "Ram Custom",
"name": "Address-custom_address_line_1_arabic-hidden",
"property": "hidden",
"property_type": "Check",
"row_name": null,
"value": "1"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "custom_areadistrict_arabic",
"is_system_generated": 1,
"modified": "2026-10-13 12:00:00.000000",
"module": "Ram Custom",
"name": "Address-custom_areadistrict_arabic-hidden",
"property": "hidden",
"property_type": "Check",
"row_name": null,
"value": "1"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "custom_citytown_arabic",
"is_system_generated": 1,
"modified": "2026-10-13 12:00:00.000000",
"module": "Ram Custom",
"name": "Address-custom_citytown_arabic-hidden",
"property": "hidden",
"property_type": "Check",
"row_name": null,
"value": "1"
}
]
9 changes: 9 additions & 0 deletions ram_custom/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def _asset_version():
"dt": "Property Setter",
"filters": [["module", "=", "Ram Custom"]],
},
{
"dt": "Print Format",
"filters": [["name", "in", ["Quotation Bilingual"]]],
},
]
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
Expand Down Expand Up @@ -197,6 +201,11 @@ def _asset_version():
"Purchase Invoice": {
"validate": "ram_custom.api.inter_company_transfer.block_inter_company_invoices",
},
"Address": {
# Mirrors the visible Arabic address fields into the older duplicates that
# print formats still read. Runs after arabic_translation's own before_save.
"before_save": "ram_custom.api.address_arabic.sync_arabic_fields",
},
"Customer": {
# on_update, NOT after_insert: the primary Address is created inside
# Customer.on_update, which runs after after_insert. See the docstring in
Expand Down
4 changes: 3 additions & 1 deletion ram_custom/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
ram_custom.patches.v0_0_2.seed_ict_reconciled_cost_value
ram_custom.patches.v0_0_2.seed_ict_uom_split_rates
ram_custom.patches.v0_0_2.seed_ict_uom_split_rates
ram_custom.patches.v0_0_3.fix_delivery_note_return_status
ram_custom.patches.v0_0_3.merge_duplicate_address_arabic_fields
Empty file.
26 changes: 26 additions & 0 deletions ram_custom/patches/v0_0_3/fix_delivery_note_return_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Ticket 5: submitted return Delivery Notes created before ERPNext started
# stamping the "Return" status still show "To Bill" in the list view.
#
# ERPNext's own rule (status_updater.py, "Delivery Note") is:
# Return = is_return == 1 and per_billed == 0 and docstatus == 1
# so this only brings old rows in line with that rule. Nothing is touched when
# per_billed > 0 (a credit note was actually raised) or when the user has
# already closed the document. `modified` is left alone so the lists don't
# reshuffle and nobody sees a phantom edit.

import frappe


def execute():
dn = frappe.qb.DocType("Delivery Note")
rows = (
frappe.qb.from_(dn)
.select(dn.name)
.where((dn.docstatus == 1) & (dn.is_return == 1) & (dn.per_billed == 0) & (dn.status == "To Bill"))
).run(pluck="name")

if not rows:
return

(frappe.qb.update(dn).set(dn.status, "Return").where(dn.name.isin(rows))).run()
frappe.logger("ram_custom").info(f"Delivery Note returns re-labelled 'Return': {len(rows)}")
44 changes: 44 additions & 0 deletions ram_custom/patches/v0_0_3/merge_duplicate_address_arabic_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Ticket 3: Address carries two parallel sets of Arabic fields.
#
# shown going forward (arabic_translation app) kept in sync (older, used by print formats)
# custom_address_line1_arabic custom_address_line_1_arabic
# custom_area_arabic custom_areadistrict_arabic
# custom_city_arabic custom_citytown_arabic
#
# The older fields are still read by live print formats, so we do not delete
# them; they are hidden on the form (Property Setters in fixtures) and kept in
# step by `ram_custom.api.address_arabic.sync_arabic_fields`. This patch makes
# the two sets agree for existing rows: whichever side is empty is filled from
# the other. Rows where both sides hold *different* text are left untouched and
# listed in the log for a person to decide.

import frappe

PAIRS = (
("custom_address_line1_arabic", "custom_address_line_1_arabic"),
("custom_area_arabic", "custom_areadistrict_arabic"),
("custom_city_arabic", "custom_citytown_arabic"),
)


def execute():
address = frappe.qb.DocType("Address")
log = frappe.logger("ram_custom")

for new, old in PAIRS:
if not (frappe.db.has_column("Address", new) and frappe.db.has_column("Address", old)):
continue

new_col, old_col = address[new], address[old]
is_empty = lambda col: (col.isnull()) | (col == "") # noqa: E731

(frappe.qb.update(address).set(new_col, old_col).where(is_empty(new_col) & ~is_empty(old_col))).run()
(frappe.qb.update(address).set(old_col, new_col).where(is_empty(old_col) & ~is_empty(new_col))).run()

differing = (
frappe.qb.from_(address)
.select(address.name)
.where(~is_empty(new_col) & ~is_empty(old_col) & (new_col != old_col))
).run(pluck="name")
if differing:
log.warning(f"Address Arabic fields differ ({new} vs {old}): {differing}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2026, Enfono Technologies and contributors
// For license information, please see license.txt

// Same filters as the standard Stock Ledger so people use it exactly the same
// way; the extra Supplier columns come from the server side.
frappe.query_reports["Stock Ledger with Supplier Details"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today(),
reqd: 1,
},
{
fieldname: "warehouse",
label: __("Warehouses"),
fieldtype: "MultiSelectList",
options: "Warehouse",
get_data: function (txt) {
const company = frappe.query_report.get_filter_value("company");
return frappe.db.get_link_options("Warehouse", txt, { company: company });
},
},
{
fieldname: "item_code",
label: __("Items"),
fieldtype: "MultiSelectList",
options: "Item",
get_data: async function (txt) {
let { message: data } = await frappe.call({
method: "erpnext.controllers.queries.item_query",
args: {
doctype: "Item",
txt: txt,
searchfield: "name",
start: 0,
page_len: 10,
filters: {},
as_dict: 1,
},
});
data = data.map(({ name, ...rest }) => ({
value: name,
description: Object.values(rest),
}));
return data || [];
},
},
{
fieldname: "item_group",
label: __("Item Group"),
fieldtype: "Link",
options: "Item Group",
},
{
fieldname: "batch_no",
label: __("Batch No"),
fieldtype: "Link",
options: "Batch",
on_change() {
const batch_no = frappe.query_report.get_filter_value("batch_no");
frappe.query_report.set_filter_value(
"segregate_serial_batch_bundle",
batch_no ? 1 : 0
);
},
},
{ fieldname: "brand", label: __("Brand"), fieldtype: "Link", options: "Brand" },
{ fieldname: "voucher_no", label: __("Voucher #"), fieldtype: "Data" },
{ fieldname: "project", label: __("Project"), fieldtype: "Link", options: "Project" },
{ fieldname: "include_uom", label: __("Include UOM"), fieldtype: "Link", options: "UOM" },
{
fieldname: "valuation_field_type",
label: __("Valuation Field Type"),
fieldtype: "Select",
width: "80",
options: "Currency\nFloat",
default: "Currency",
},
{
fieldname: "segregate_serial_batch_bundle",
label: __("Segregate Serial / Batch Bundle"),
fieldtype: "Check",
default: 0,
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname == "out_qty" && data && data.out_qty < 0) {
value = "<span style='color:red'>" + value + "</span>";
} else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
return value;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"add_total_row": 0,
"columns": [],
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letterhead": null,
"modified": "2026-10-13 12:00:00.000000",
"module": "Ram Custom",
"name": "Stock Ledger with Supplier Details",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Stock Ledger with Supplier Details",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Accounts Manager"
}
]
}
Loading
Loading