Add a manual, dry-run-first setup script for the Ugandan company
The live company was created as Kenya/KES. setup_uganda.run creates a UGX company on the Standard chart with a July-June fiscal year, VAT Output/Input accounts with 18% sales and purchase templates, stage WIP, Quarantine, Returns Hold and Rejected warehouses, and sets Manufacturing Settings to backflush on transferred material with enforced time logs (B1, M7, M8). It only creates, never deletes, and does nothing without confirm=1. It is deliberately not an after_migrate hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""One-off setup of the Ugandan company and its operating structure (backlog 1, 16, 17).
|
||||
|
||||
Never runs on migrate. Run it by hand, dry-run first:
|
||||
|
||||
bench --site <site> execute makap_backend.makap_backend.setup_uganda.run
|
||||
bench --site <site> execute makap_backend.makap_backend.setup_uganda.run --kwargs "{'confirm': 1}"
|
||||
|
||||
It creates and never deletes. The original Kenya/KES company is left alone:
|
||||
once the new company is checked, remove the old one in the desk (it has no
|
||||
transactions) so nobody posts to it by mistake.
|
||||
|
||||
Choices below are recommended defaults for Makap to confirm, not decisions
|
||||
taken for them: company name, abbreviation, July-June fiscal year, VAT at 18%.
|
||||
"""
|
||||
|
||||
import frappe
|
||||
from frappe.utils import getdate
|
||||
|
||||
COUNTRY = "Uganda"
|
||||
CURRENCY = "UGX"
|
||||
TIME_ZONE = "Africa/Kampala"
|
||||
VAT_RATE = 18
|
||||
|
||||
# Added beside the four warehouses ERPNext creates with every company
|
||||
# (Stores, Work In Progress, Finished Goods, Goods In Transit).
|
||||
WAREHOUSES = [
|
||||
("Raw Materials", {}),
|
||||
("Quarantine", {}),
|
||||
("WIP - Printing", {}),
|
||||
("WIP - Lamination and Curing", {}),
|
||||
("WIP - Slitting", {}),
|
||||
("WIP - Bag Making", {}),
|
||||
("Returns Hold", {"makap_is_returns_hold_warehouse": 1}),
|
||||
("Rejected", {}),
|
||||
]
|
||||
|
||||
|
||||
class Plan:
|
||||
def __init__(self, confirm):
|
||||
self.confirm = confirm
|
||||
self.steps = []
|
||||
|
||||
def do(self, description, action):
|
||||
self.steps.append(("DONE " if self.confirm else "WOULD ") + description)
|
||||
if self.confirm:
|
||||
action()
|
||||
|
||||
def skip(self, description):
|
||||
self.steps.append("SKIP " + description)
|
||||
|
||||
|
||||
def run(company_name="Makap Uganda Limited", abbr="MUL", fiscal_year_start="2026-07-01", confirm=0):
|
||||
plan = Plan(bool(int(confirm)))
|
||||
|
||||
_system_settings(plan)
|
||||
_company(plan, company_name, abbr)
|
||||
_fiscal_year(plan, company_name, getdate(fiscal_year_start))
|
||||
_vat(plan, company_name, abbr)
|
||||
_warehouses(plan, company_name, abbr)
|
||||
_manufacturing_and_stock_settings(plan, abbr)
|
||||
|
||||
if plan.confirm:
|
||||
frappe.db.commit()
|
||||
print("\n".join(plan.steps))
|
||||
if not plan.confirm:
|
||||
print("\nDry run only. Re-run with --kwargs \"{'confirm': 1}\" to apply.")
|
||||
return plan.steps
|
||||
|
||||
|
||||
def _system_settings(plan):
|
||||
def apply():
|
||||
if not frappe.db.get_value("Currency", CURRENCY, "enabled"):
|
||||
frappe.db.set_value("Currency", CURRENCY, "enabled", 1)
|
||||
settings = frappe.get_single("System Settings")
|
||||
settings.country = COUNTRY
|
||||
settings.time_zone = TIME_ZONE
|
||||
settings.currency = CURRENCY
|
||||
settings.save()
|
||||
|
||||
plan.do(f"set System Settings to {COUNTRY}, {TIME_ZONE}, {CURRENCY}", apply)
|
||||
|
||||
|
||||
def _company(plan, company_name, abbr):
|
||||
if frappe.db.exists("Company", company_name):
|
||||
plan.skip(f"company {company_name} already exists")
|
||||
return
|
||||
|
||||
def apply():
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"country": COUNTRY,
|
||||
"default_currency": CURRENCY,
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
"chart_of_accounts": "Standard",
|
||||
}
|
||||
).insert()
|
||||
defaults = frappe.get_single("Global Defaults")
|
||||
defaults.default_company = company_name
|
||||
defaults.default_currency = CURRENCY
|
||||
defaults.country = COUNTRY
|
||||
defaults.save()
|
||||
|
||||
plan.do(f"create company {company_name} ({abbr}) in {CURRENCY} with the Standard chart, and make it the default", apply)
|
||||
|
||||
|
||||
def _fiscal_year(plan, company_name, start):
|
||||
end = frappe.utils.add_days(frappe.utils.add_months(start, 12), -1)
|
||||
overlapping = frappe.get_all(
|
||||
"Fiscal Year",
|
||||
filters={"year_start_date": ["<=", end], "year_end_date": [">=", start]},
|
||||
pluck="name",
|
||||
)
|
||||
if overlapping:
|
||||
plan.skip(
|
||||
f"fiscal year {start}..{end}: overlaps {', '.join(overlapping)}. "
|
||||
"Add the company to it, or adjust it, in the desk"
|
||||
)
|
||||
return
|
||||
|
||||
def apply():
|
||||
doc = frappe.new_doc("Fiscal Year")
|
||||
doc.year = f"{start.year}-{end.year}"
|
||||
doc.year_start_date = start
|
||||
doc.year_end_date = end
|
||||
doc.append("companies", {"company": company_name})
|
||||
doc.insert()
|
||||
|
||||
plan.do(f"create fiscal year {start}..{end} for {company_name}", apply)
|
||||
|
||||
|
||||
def _tax_account(company_name, abbr, account_name):
|
||||
parent = frappe.db.get_value(
|
||||
"Account", {"company": company_name, "account_name": "Duties and Taxes", "is_group": 1}, "name"
|
||||
)
|
||||
if not parent:
|
||||
frappe.throw(f"No 'Duties and Taxes' group in {company_name}'s chart of accounts.")
|
||||
name = f"{account_name} - {abbr}"
|
||||
if not frappe.db.exists("Account", name):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": account_name,
|
||||
"parent_account": parent,
|
||||
"company": company_name,
|
||||
"account_type": "Tax",
|
||||
"tax_rate": VAT_RATE,
|
||||
}
|
||||
).insert()
|
||||
return name
|
||||
|
||||
|
||||
def _vat(plan, company_name, abbr):
|
||||
def apply():
|
||||
for doctype, account_name, title in (
|
||||
("Sales Taxes and Charges Template", "VAT Output", "Uganda VAT 18%"),
|
||||
("Purchase Taxes and Charges Template", "VAT Input", "Uganda VAT 18%"),
|
||||
):
|
||||
account = _tax_account(company_name, abbr, account_name)
|
||||
if frappe.db.exists(doctype, {"title": title, "company": company_name}):
|
||||
continue
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": doctype,
|
||||
"title": title,
|
||||
"company": company_name,
|
||||
"is_default": 1,
|
||||
"taxes": [
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": account,
|
||||
"description": f"VAT @ {VAT_RATE}%",
|
||||
"rate": VAT_RATE,
|
||||
**({"category": "Total", "add_deduct_tax": "Add"} if doctype.startswith("Purchase") else {}),
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert()
|
||||
|
||||
plan.do(f"create VAT Output/Input accounts and default {VAT_RATE}% sales and purchase tax templates", apply)
|
||||
|
||||
|
||||
def _warehouses(plan, company_name, abbr):
|
||||
def apply():
|
||||
parent = frappe.db.get_value("Warehouse", {"company": company_name, "is_group": 1, "parent_warehouse": ["is", "not set"]})
|
||||
for name, extra in WAREHOUSES:
|
||||
if frappe.db.exists("Warehouse", f"{name} - {abbr}"):
|
||||
continue
|
||||
frappe.get_doc(
|
||||
{"doctype": "Warehouse", "warehouse_name": name, "company": company_name, "parent_warehouse": parent, **extra}
|
||||
).insert()
|
||||
|
||||
plan.do("create warehouses: " + ", ".join(name for name, _ in WAREHOUSES), apply)
|
||||
|
||||
|
||||
def _manufacturing_and_stock_settings(plan, abbr):
|
||||
def apply():
|
||||
manufacturing = frappe.get_single("Manufacturing Settings")
|
||||
manufacturing.backflush_raw_materials_based_on = "Material Transferred for Manufacture"
|
||||
manufacturing.enforce_time_logs = 1
|
||||
manufacturing.default_wip_warehouse = f"Work In Progress - {abbr}"
|
||||
manufacturing.default_fg_warehouse = f"Finished Goods - {abbr}"
|
||||
manufacturing.save()
|
||||
|
||||
stock = frappe.get_single("Stock Settings")
|
||||
stock.enable_stock_reservation = 1
|
||||
stock.save()
|
||||
|
||||
plan.do(
|
||||
"Manufacturing Settings: backflush on Material Transferred, enforce Job Card time logs, "
|
||||
"default WIP/FG warehouses; Stock Settings: enable stock reservation",
|
||||
apply,
|
||||
)
|
||||
Reference in New Issue
Block a user