didi-website-erp-crm/erp_crm/scripts/setup/09-phase2-final.py

288 lines
11 KiB
Python

"""
ERPNext Phase 2 Final Setup: Buying, API User, Reports
"""
import frappe
import json
def execute():
setup_buying()
setup_api_user()
setup_print_format()
frappe.db.commit()
print("\n=== Phase 2 Final Setup Complete ===\n")
def setup_buying():
"""2.6 - Configure suppliers and expense categories."""
print("[1/3] Configuring suppliers and expenses...")
company_abbr = "TC"
# Create expense accounts under existing parent
expenses_parent = frappe.db.get_value("Account",
{"company": "TOP CLOSSERS SRL", "root_type": "Expense", "is_group": 1, "parent_account": ["like", "%Expenses%"]},
"name"
)
if not expenses_parent:
expenses_parent = frappe.db.get_value("Account",
{"company": "TOP CLOSSERS SRL", "root_type": "Expense", "is_group": 1},
"name"
)
if expenses_parent:
expense_accounts = [
{"account_name": "Hosting si Infrastructura", "account_type": "Expense Account"},
{"account_name": "Servicii Software", "account_type": "Expense Account"},
{"account_name": "Marketing si Publicitate", "account_type": "Expense Account"},
{"account_name": "Servicii Plati (Stripe)", "account_type": "Expense Account"},
]
for acc in expense_accounts:
full_name = f"{acc['account_name']} - {company_abbr}"
if not frappe.db.exists("Account", full_name):
try:
a = frappe.get_doc({
"doctype": "Account",
"account_name": acc["account_name"],
"parent_account": expenses_parent,
"account_type": acc["account_type"],
"company": "TOP CLOSSERS SRL",
})
a.insert(ignore_permissions=True)
print(f" Created account: {acc['account_name']}")
except Exception as e:
print(f" Account {acc['account_name']}: {e}")
# Create suppliers
suppliers = [
{"supplier_name": "Hetzner Online GmbH", "supplier_group": "Services", "country": "Germany",
"supplier_type": "Company"},
{"supplier_name": "Stripe Payments Europe", "supplier_group": "Services", "country": "Ireland",
"supplier_type": "Company"},
{"supplier_name": "Twilio SendGrid", "supplier_group": "Services", "country": "United States",
"supplier_type": "Company"},
]
# Ensure supplier group exists
if not frappe.db.exists("Supplier Group", "Services"):
sg = frappe.get_doc({"doctype": "Supplier Group", "supplier_group_name": "Services"})
sg.insert(ignore_permissions=True)
for s in suppliers:
if not frappe.db.exists("Supplier", s["supplier_name"]):
doc = frappe.get_doc({"doctype": "Supplier", **s})
doc.insert(ignore_permissions=True)
print(f" Created supplier: {s['supplier_name']}")
print(" Buying configured.")
def setup_api_user():
"""2.9 - Create dedicated API user for website integration."""
print("[2/3] Creating API user and role...")
# Create custom role
if not frappe.db.exists("Role", "Website Integration"):
role = frappe.get_doc({
"doctype": "Role",
"role_name": "Website Integration",
"desk_access": 0,
"is_custom": 1
})
role.insert(ignore_permissions=True)
print(" Created role: Website Integration")
# Set permissions for the role
doctypes_read_write = [
"Customer", "Lead", "Sales Invoice", "Payment Entry", "Subscription",
"Subscription Plan", "Website Content", "Service Agreement",
"Analysis Report",
"Payment Log", "Item", "Address"
]
doctypes_read_only = [
"Company", "Account", "Sales Taxes and Charges Template",
"Email Template", "Sales Stage"
]
for dt in doctypes_read_write:
try:
# Check if DocType exists
if not frappe.db.exists("DocType", dt):
continue
existing_name = frappe.db.exists("Custom DocPerm", {"parent": dt, "role": "Website Integration"})
values = {
"read": 1,
"write": 1,
"create": 1,
"delete": 0,
"email": 0,
"print": 1,
"export": 1,
"submit": 1 if dt in ("Sales Invoice", "Payment Entry") else 0,
}
if existing_name:
frappe.db.set_value("Custom DocPerm", existing_name, values, update_modified=False)
else:
perm = frappe.get_doc({
"doctype": "Custom DocPerm",
"parent": dt,
"parenttype": "DocType",
"parentfield": "permissions",
"role": "Website Integration",
**values,
})
perm.insert(ignore_permissions=True)
except Exception:
pass
for dt in doctypes_read_only:
if not frappe.db.exists("Custom DocPerm", {"parent": dt, "role": "Website Integration"}):
try:
if frappe.db.exists("DocType", dt):
perm = frappe.get_doc({
"doctype": "Custom DocPerm",
"parent": dt,
"parenttype": "DocType",
"parentfield": "permissions",
"role": "Website Integration",
"read": 1,
"write": 0,
"create": 0,
})
perm.insert(ignore_permissions=True)
except Exception:
pass
# Create API user
api_user_email = "website_api@didi.localhost"
if not frappe.db.exists("User", api_user_email):
user = frappe.get_doc({
"doctype": "User",
"email": api_user_email,
"first_name": "Website",
"last_name": "API",
"enabled": 1,
"user_type": "System User",
"roles": [
{"role": "Website Integration"},
],
"new_password": "didi_api_secure_pwd_2026!",
"send_welcome_email": 0,
})
user.insert(ignore_permissions=True)
print(f" Created API user: {api_user_email}")
# Generate API keys
api_secret = frappe.generate_hash(length=15)
user.reload()
user.api_key = frappe.generate_hash(length=15)
user.api_secret = api_secret
user.save(ignore_permissions=True)
print(f" API Key: {user.api_key}")
print(f" API Secret: {api_secret}")
print(f" >>> Save these credentials in website/.env.local <<<")
else:
user = frappe.get_doc("User", api_user_email)
print(f" API user already exists: {api_user_email}")
print(f" API Key: {user.api_key}")
print(" API user configured.")
def setup_print_format():
"""2.3.2 - Create custom invoice PDF print format."""
print("[3/3] Creating custom invoice print format...")
if frappe.db.exists("Print Format", "DiDi Invoice"):
print(" Already exists, skipping.")
return
html = """
<style>
.didi-invoice { font-family: Arial, sans-serif; font-size: 12px; color: #333; }
.didi-invoice .header { display: flex; justify-content: space-between; margin-bottom: 30px; }
.didi-invoice .company-info { text-align: right; }
.didi-invoice .invoice-title { font-size: 24px; font-weight: bold; color: #2563eb; margin-bottom: 5px; }
.didi-invoice table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.didi-invoice th { background: #2563eb; color: white; padding: 8px 12px; text-align: left; }
.didi-invoice td { padding: 8px 12px; border-bottom: 1px solid #e5e7eb; }
.didi-invoice .totals { text-align: right; margin-top: 20px; }
.didi-invoice .totals td { font-weight: bold; }
.didi-invoice .footer { margin-top: 40px; padding-top: 20px; border-top: 2px solid #2563eb; font-size: 10px; color: #666; }
</style>
<div class="didi-invoice">
<div class="header">
<div>
<div class="invoice-title">FACTURA</div>
<div><strong>{{ doc.name }}</strong></div>
<div>Data: {{ doc.posting_date }}</div>
<div>Scadenta: {{ doc.due_date }}</div>
</div>
<div class="company-info">
<div><strong>{{ doc.company }}</strong></div>
<div>CUI: {{ frappe.db.get_value("Company", doc.company, "tax_id") }}</div>
<div>{{ frappe.db.get_value("Company", doc.company, "address") or "Str. Targovistei 15, Ploiesti" }}</div>
</div>
</div>
<div style="margin-bottom: 20px;">
<strong>Catre:</strong><br>
{{ doc.customer_name }}<br>
{% if doc.tax_id %}CUI: {{ doc.tax_id }}<br>{% endif %}
{{ doc.address_display or "" }}
</div>
<table>
<thead>
<tr>
<th>Nr.</th>
<th>Descriere</th>
<th>Cant.</th>
<th>Pret unitar</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for item in doc.items %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ item.item_name }}<br><small>{{ item.description or "" }}</small></td>
<td>{{ item.qty }}</td>
<td>{{ frappe.format(item.rate, {"fieldtype": "Currency", "currency": doc.currency}) }}</td>
<td>{{ frappe.format(item.amount, {"fieldtype": "Currency", "currency": doc.currency}) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="totals" style="width: 300px; margin-left: auto;">
<tr><td>Subtotal:</td><td>{{ frappe.format(doc.net_total, {"fieldtype": "Currency", "currency": doc.currency}) }}</td></tr>
{% for tax in doc.taxes %}
<tr><td>{{ tax.description }}:</td><td>{{ frappe.format(tax.tax_amount, {"fieldtype": "Currency", "currency": doc.currency}) }}</td></tr>
{% endfor %}
<tr style="font-size: 16px;"><td>TOTAL:</td><td>{{ frappe.format(doc.grand_total, {"fieldtype": "Currency", "currency": doc.currency}) }}</td></tr>
</table>
<div class="footer">
<p>Factura generata automat de platforma DiDi. Acest document este valid fara semnatura si stampila conform art. 106 alin. 2 din Legea 227/2015.</p>
<p>TOP CLOSSERS SRL | CUI: 36193026 | J2022000345035 | office@clossers.com</p>
</div>
</div>
"""
pf = frappe.get_doc({
"doctype": "Print Format",
"name": "DiDi Invoice",
"doc_type": "Sales Invoice",
"module": "Didi Custom",
"html": html,
"print_format_type": "Jinja",
"standard": "No",
"custom_format": 1,
"default_print_language": "ro",
})
pf.insert(ignore_permissions=True)
print(" DiDi Invoice print format created.")