Files
default_thai_company/default_thai_company/shipping.py
T
kurogeek 8cba58ba02 feat: default Shipping Rule per Thai company
"Shipping Charges - <abbr>": Selling, Fixed, posting to a "Shipping
Charges" income account under Direct Income (created if the chart lacks
it) with the default cost center. Shipping Rule is named by its label,
so the company abbreviation keeps one rule per company, as ERPNext
names its tax templates. Created on Company save, after install, and by
a patch for existing companies.

The amount is entered on the transaction, not the rule: ERPNext
re-applies the rule on every recalculation and would reset the charge
row to the rule's fixed amount. ThaiShippingRule overrides the doctype
class so a Fixed rule with no amount only seeds the row and leaves the
entered amount alone.
2026-09-17 10:02:23 +00:00

67 lines
2.4 KiB
Python

import erpnext
import frappe
from erpnext.accounts.doctype.shipping_rule.shipping_rule import ShippingRule
from default_thai_company.tax_withholding import company_ready, get_or_create_account, thai_companies
# Shipping Rule is named by its label (site-wide unique) but bound to one company,
# so each Thai company gets "<LABEL> - <abbr>", the way ERPNext names accounts and
# tax templates. The label doubles as the charge description on the transaction.
LABEL = "Shipping Charges"
# Shipping billed to customers is revenue; the courier's bill stays an expense.
SHIPPING_ACCOUNT = "Shipping Charges"
SHIPPING_ACCOUNT_GROUPS = ("Direct Income", "Income")
class ThaiShippingRule(ShippingRule):
def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
"""A Fixed rule without an amount only seeds the charge row; the amount is
entered on the transaction. ERPNext re-applies the rule on every
recalculation, which would otherwise reset the row to 0."""
manual = self.calculate_based_on == "Fixed" and not self.shipping_amount
entered = [(row, row.tax_amount) for row in doc.get("taxes")] if manual else ()
super().add_shipping_rule_to_tax_table(doc, shipping_amount)
for row, amount in entered:
row.tax_amount = amount
def shipping_rule_name(company):
abbr = frappe.get_cached_value("Company", company, "abbr")
return f"{LABEL} - {abbr}"
def ensure_shipping_rule(company):
"""Selling Shipping Rule for `company` with the amount entered per
transaction (see ThaiShippingRule). No-op when the rule exists."""
name = shipping_rule_name(company)
if frappe.db.exists("Shipping Rule", name):
return
frappe.get_doc(
{
"doctype": "Shipping Rule",
"label": name,
"company": company,
"shipping_rule_type": "Selling",
"calculate_based_on": "Fixed",
"shipping_amount": 0,
"account": get_or_create_account(
company, SHIPPING_ACCOUNT, "Income", "Income Account", SHIPPING_ACCOUNT_GROUPS
),
"cost_center": erpnext.get_default_cost_center(company),
}
).insert(ignore_permissions=True)
def setup_company(doc, method=None):
"""Company.on_update: default Shipping Rule for a Thai company."""
if company_ready(doc):
ensure_shipping_rule(doc.name)
def setup_companies():
"""after_install and the create_shipping_rules patch: companies that exist
before this code did never pass through `setup_company`."""
for company in thai_companies():
ensure_shipping_rule(company)