288 lines
11 KiB
Python
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.")
|