Close review gaps: gate fixes, cylinder register, roll tracking, costing, Uganda setup #1

Merged
denis_ireri merged 8 commits from feat/review-backlog-fixes into develop 2026-10-10 10:02:47 +00:00
Showing only changes of commit fc4632ab4d - Show all commits
+215
View File
@@ -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,
)