diff --git a/rmax_custom/inter_branch.py b/rmax_custom/inter_branch.py index d9de138..cdf55b6 100644 --- a/rmax_custom/inter_branch.py +++ b/rmax_custom/inter_branch.py @@ -443,6 +443,30 @@ def resolve_warehouse_branch(warehouse: str) -> str | None: return rows[0][0] if rows else None +def resolve_branch_cost_center(branch: str) -> str | None: + """Look up the default Cost Center for a Branch via Branch Configuration. + + Mirrors resolve_warehouse_branch: returns the first (idx-ordered, i.e. + the "default" row per the Branch Configuration Cost Center convention) + Cost Center of the Branch Configuration linked to this branch, or None + when no mapping exists. + """ + if not branch: + return None + rows = frappe.db.sql( + """ + SELECT bcc.cost_center + FROM `tabBranch Configuration Cost Center` bcc + INNER JOIN `tabBranch Configuration` bc ON bc.name = bcc.parent + WHERE bc.branch = %s AND bcc.cost_center IS NOT NULL + ORDER BY bcc.idx ASC + LIMIT 1 + """, + (branch,), + ) + return rows[0][0] if rows else None + + @frappe.whitelist() def backfill_je_header_source() -> int: """Populate `custom_source_doctype` and `custom_source_docname` on the Journal diff --git a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.js b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.js index e958019..14376a6 100644 --- a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.js +++ b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.js @@ -6,6 +6,7 @@ frappe.ui.form.on('Inter Branch Stock Transfer', { onload: function (frm) { _setup_warehouse_queries(frm); _setup_price_list_query(frm); + _setup_taxes_and_charges_query(frm); if (frm.is_new()) { _set_default_source_warehouse(frm); } @@ -14,6 +15,7 @@ frappe.ui.form.on('Inter Branch Stock Transfer', { refresh: function (frm) { _setup_warehouse_queries(frm); _setup_price_list_query(frm); + _setup_taxes_and_charges_query(frm); _setup_buttons(frm); _toggle_posting_time(frm); }, @@ -24,6 +26,8 @@ frappe.ui.form.on('Inter Branch Stock Transfer', { company: function (frm) { _setup_warehouse_queries(frm); + _setup_taxes_and_charges_query(frm); + _set_default_taxes_and_charges(frm); }, from_warehouse: function (frm) { @@ -92,7 +96,9 @@ frappe.ui.form.on('Inter Branch Stock Transfer Item', { frappe.model.set_value(cdt, cdn, 't_warehouse', frm.doc.to_warehouse); } - _fetch_valuation_rate(frm, cdt, cdn); + _fetch_price_list_rate(frm, cdt, cdn, function () { + _fetch_valuation_rate(frm, cdt, cdn); + }); }, s_warehouse: function (frm, cdt, cdn) { @@ -252,9 +258,32 @@ function _setup_price_list_query(frm) { }); } -function _fetch_price_list_rate(frm, cdt, cdn) { +function _setup_taxes_and_charges_query(frm) { + frm.set_query('taxes_and_charges', function () { + return { filters: { company: frm.doc.company, disabled: 0 } }; + }); +} + +function _set_default_taxes_and_charges(frm) { + if (!frm.doc.company || frm.doc.taxes_and_charges) return; + frappe.db.get_value( + 'Sales Taxes and Charges Template', + { company: frm.doc.company, is_default: 1 }, + 'name', + function (r) { + if (r && r.name) { + frm.set_value('taxes_and_charges', r.name); + } + } + ); +} + +function _fetch_price_list_rate(frm, cdt, cdn, on_not_found) { let row = locals[cdt][cdn]; - if (!row.item_code || !frm.doc.price_list) return; + if (!row.item_code || !frm.doc.price_list) { + if (on_not_found) on_not_found(); + return; + } frappe.call({ method: 'frappe.client.get_value', args: { @@ -267,6 +296,8 @@ function _fetch_price_list_rate(frm, cdt, cdn) { frappe.model.set_value(cdt, cdn, 'basic_rate', r.message.price_list_rate); frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message.price_list_rate); _recalculate_row_amount(cdt, cdn); + } else if (on_not_found) { + on_not_found(); } } }); diff --git a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.json b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.json index c270af0..0f755bb 100644 --- a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.json +++ b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.json @@ -21,6 +21,7 @@ "customer", "customer_name", "price_list", + "taxes_and_charges", "section_break_items", "items", "section_break_refs", @@ -99,7 +100,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Set Source Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "reqd": 1 }, { "fieldname": "column_break_wh", @@ -110,7 +112,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Set Target Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "reqd": 1 }, { "fieldname": "section_break_customer", @@ -121,7 +124,8 @@ "fieldname": "customer", "fieldtype": "Link", "label": "Customer", - "options": "Customer" + "options": "Customer", + "reqd": 1 }, { "fetch_from": "customer.customer_name", @@ -136,6 +140,12 @@ "label": "Price List", "options": "Price List" }, + { + "fieldname": "taxes_and_charges", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "options": "Sales Taxes and Charges Template" + }, { "fieldname": "section_break_items", "fieldtype": "Section Break" @@ -189,7 +199,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-07-01 00:00:00.000000", + "modified": "2026-07-02 10:42:59.541116", "modified_by": "Administrator", "module": "Rmax Custom", "name": "Inter Branch Stock Transfer", @@ -243,4 +253,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.py b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.py index 7bee1c9..9ffd163 100644 --- a/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.py +++ b/rmax_custom/rmax_custom/doctype/inter_branch_stock_transfer/inter_branch_stock_transfer.py @@ -14,6 +14,7 @@ class InterBranchStockTransfer(Document): def validate(self): self._set_item_warehouses_from_header() + self._set_default_taxes_and_charges() def _set_item_warehouses_from_header(self): """Push header warehouse defaults to item rows that have no row-level value.""" @@ -23,6 +24,13 @@ def _set_item_warehouses_from_header(self): if self.to_warehouse and not item.t_warehouse: item.t_warehouse = self.to_warehouse + def _set_default_taxes_and_charges(self): + """Default to the company's default Sales Taxes and Charges Template, if unset.""" + if self.is_new() and self.company and not self.taxes_and_charges: + self.taxes_and_charges = frappe.db.get_value( + "Sales Taxes and Charges Template", {"is_default": 1, "company": self.company} + ) + def before_submit(self): self._validate_warehouses() self._validate_stock_availability() @@ -89,11 +97,12 @@ def _validate_stock_availability(self): # ------------------------------------------------------------------ def on_submit(self): - self._create_stock_entry() + source_branch, target_branch = self._resolve_branches_or_throw() + self._create_stock_entry(source_branch, target_branch) if self.customer: - self._create_delivery_note() + self._create_delivery_note(target_branch) try: - self._create_journal_entry() + self._create_journal_entry(source_branch, target_branch) except Exception: frappe.log_error( title="Inter-Branch companion JE failed (IBST)", @@ -101,20 +110,15 @@ def on_submit(self): ) raise - def _create_journal_entry(self) -> str | None: - """Create and submit the inter-branch companion JE for this IBST. - - Identical pattern to create_companion_inter_branch_je_for_stock_transfer: - Dr Due from (branch = source_branch) - Cr Due to (branch = target_branch) + def _resolve_branches_or_throw(self) -> tuple[str, str]: + """Resolve (source_branch, target_branch) from Branch Configuration warehouse mapping. - Requires from_warehouse and to_warehouse to be mapped in Branch - Configuration so resolve_warehouse_branch can identify the branches. + Both warehouses must be mapped, or GL posting for the companion Stock + Entry / Delivery Note fails deep inside submit() with an opaque + "Accounting Dimension Branch is required" error instead of this + actionable one. """ - from rmax_custom.inter_branch import ( - resolve_warehouse_branch, - get_or_create_inter_branch_account, - ) + from rmax_custom.inter_branch import resolve_warehouse_branch source_branch = resolve_warehouse_branch(self.from_warehouse) target_branch = resolve_warehouse_branch(self.to_warehouse) @@ -122,14 +126,28 @@ def _create_journal_entry(self) -> str | None: if not source_branch or not target_branch: frappe.throw( _( - "Journal Entry cannot be created because the branch could not be " - "determined for one or both warehouses.

" + "Branch could not be determined for one or both warehouses.

" "Go to Branch Configuration and add:
" "• {0} under the Source Branch's Warehouse table
" "• {1} under the Target Branch's Warehouse table" ).format(self.from_warehouse or "—", self.to_warehouse or "—"), title=_("Warehouse Not Mapped to Branch"), ) + return source_branch, target_branch + + def _create_journal_entry( + self, source_branch: str | None = None, target_branch: str | None = None + ) -> str | None: + """Create and submit the inter-branch companion JE for this IBST. + + Identical pattern to create_companion_inter_branch_je_for_stock_transfer: + Dr Due from (branch = source_branch) + Cr Due to (branch = target_branch) + """ + from rmax_custom.inter_branch import get_or_create_inter_branch_account + + if source_branch is None or target_branch is None: + source_branch, target_branch = self._resolve_branches_or_throw() if source_branch == target_branch: return None @@ -204,10 +222,12 @@ def _create_journal_entry(self) -> str | None: ) return je.name - def _create_stock_entry(self): + def _create_stock_entry(self, source_branch: str, target_branch: str): se = frappe.new_doc("Stock Entry") se.stock_entry_type = "Material Transfer" se.company = self.company + se.branch = source_branch + se.set_posting_time = 1 se.posting_date = self.posting_date se.posting_time = self.posting_time se.from_warehouse = self.from_warehouse @@ -226,6 +246,9 @@ def _create_stock_entry(self): "allow_zero_valuation_rate": item.allow_zero_valuation_rate, "serial_no": item.serial_no, "batch_no": item.batch_no, + # Both legs are stamped with source_branch so GL insert passes the + # mandatory-dimension check; the target leg is corrected below. + "branch": source_branch, }) # Prevent the SE on_submit hook from creating a second inter-branch JE. @@ -237,6 +260,14 @@ def _create_stock_entry(self): se.flags.ignore_permissions = True se.submit() + if source_branch != target_branch: + # on_stock_entry_submit (which normally does this) is skipped for SEs + # flagged from_stock_transfer, so re-tag the target warehouse's GL + # leg(s) here instead. + from rmax_custom.inter_branch import _retag_se_gl_entries + + _retag_se_gl_entries(se, source_branch, target_branch) + self.db_set("stock_entry", se.name, notify=True) frappe.msgprint( _('Stock Entry {0} created').format(se.name), @@ -244,10 +275,20 @@ def _create_stock_entry(self): indicator="green", ) - def _create_delivery_note(self): + def _create_delivery_note(self, target_branch: str | None = None): + if target_branch is None: + target_branch = self._resolve_branches_or_throw()[1] + + from rmax_custom.inter_branch import resolve_branch_cost_center + + target_cost_center = resolve_branch_cost_center(target_branch) + dn = frappe.new_doc("Delivery Note") dn.customer = self.customer dn.company = self.company + dn.branch = target_branch + dn.cost_center = target_cost_center + dn.set_posting_time = 1 dn.posting_date = self.posting_date dn.posting_time = self.posting_time dn.set_warehouse = self.to_warehouse @@ -261,11 +302,23 @@ def _create_delivery_note(self): "uom": item.uom, "warehouse": item.t_warehouse or self.to_warehouse, "rate": item.basic_rate, + "cost_center": target_cost_center, "description": item.description or item.item_name, + "branch": target_branch, }) + if self.taxes_and_charges: + from erpnext.controllers.accounts_controller import get_taxes_and_charges + + dn.taxes_and_charges = self.taxes_and_charges + dn.extend( + "taxes", + get_taxes_and_charges("Sales Taxes and Charges Template", self.taxes_and_charges), + ) + dn.flags.ignore_permissions = True dn.insert(ignore_permissions=True) + dn.submit() self.db_set("delivery_note", dn.name, notify=True) frappe.msgprint(