You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

951 line
28 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: GNU General Public License v3. See license.txt
  3. import frappe
  4. from frappe import _, msgprint, scrub
  5. from frappe.contacts.doctype.address.address import (
  6. get_address_display,
  7. get_company_address,
  8. get_default_address,
  9. )
  10. from frappe.contacts.doctype.contact.contact import get_contact_details
  11. from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
  12. from frappe.model.utils import get_fetch_values
  13. from frappe.utils import (
  14. add_days,
  15. add_months,
  16. add_years,
  17. cint,
  18. cstr,
  19. date_diff,
  20. flt,
  21. formatdate,
  22. get_last_day,
  23. get_timestamp,
  24. getdate,
  25. nowdate,
  26. )
  27. import erpnext
  28. from erpnext import get_company_currency
  29. from erpnext.accounts.utils import get_fiscal_year
  30. from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
  31. PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
  32. SALES_TRANSACTION_TYPES = {
  33. "Quotation",
  34. "Sales Order",
  35. "Delivery Note",
  36. "Sales Invoice",
  37. "POS Invoice",
  38. }
  39. TRANSACTION_TYPES = PURCHASE_TRANSACTION_TYPES | SALES_TRANSACTION_TYPES
  40. class DuplicatePartyAccountError(frappe.ValidationError):
  41. pass
  42. @frappe.whitelist()
  43. def get_party_details(
  44. party=None,
  45. account=None,
  46. party_type="Customer",
  47. company=None,
  48. posting_date=None,
  49. bill_date=None,
  50. price_list=None,
  51. currency=None,
  52. doctype=None,
  53. ignore_permissions=False,
  54. fetch_payment_terms_template=True,
  55. party_address=None,
  56. company_address=None,
  57. shipping_address=None,
  58. pos_profile=None,
  59. ):
  60. if not party:
  61. return {}
  62. if not frappe.db.exists(party_type, party):
  63. frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
  64. return _get_party_details(
  65. party,
  66. account,
  67. party_type,
  68. company,
  69. posting_date,
  70. bill_date,
  71. price_list,
  72. currency,
  73. doctype,
  74. ignore_permissions,
  75. fetch_payment_terms_template,
  76. party_address,
  77. company_address,
  78. shipping_address,
  79. pos_profile,
  80. )
  81. def _get_party_details(
  82. party=None,
  83. account=None,
  84. party_type="Customer",
  85. company=None,
  86. posting_date=None,
  87. bill_date=None,
  88. price_list=None,
  89. currency=None,
  90. doctype=None,
  91. ignore_permissions=False,
  92. fetch_payment_terms_template=True,
  93. party_address=None,
  94. company_address=None,
  95. shipping_address=None,
  96. pos_profile=None,
  97. ):
  98. party_details = frappe._dict(
  99. set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
  100. )
  101. party = party_details[party_type.lower()]
  102. if not ignore_permissions and not (
  103. frappe.has_permission(party_type, "read", party)
  104. or frappe.has_permission(party_type, "select", party)
  105. ):
  106. frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
  107. party = frappe.get_doc(party_type, party)
  108. currency = party.get("default_currency") or currency or get_company_currency(company)
  109. party_address, shipping_address = set_address_details(
  110. party_details,
  111. party,
  112. party_type,
  113. doctype,
  114. company,
  115. party_address,
  116. company_address,
  117. shipping_address,
  118. )
  119. set_contact_details(party_details, party, party_type)
  120. set_other_values(party_details, party, party_type)
  121. set_price_list(party_details, party, party_type, price_list, pos_profile)
  122. tax_template = set_taxes(
  123. party.name,
  124. party_type,
  125. posting_date,
  126. company,
  127. customer_group=party_details.customer_group,
  128. supplier_group=party_details.supplier_group,
  129. tax_category=party_details.tax_category,
  130. billing_address=party_address,
  131. shipping_address=shipping_address,
  132. )
  133. if tax_template:
  134. party_details["taxes_and_charges"] = tax_template
  135. if cint(fetch_payment_terms_template):
  136. party_details["payment_terms_template"] = get_payment_terms_template(
  137. party.name, party_type, company
  138. )
  139. if not party_details.get("currency"):
  140. party_details["currency"] = currency
  141. # sales team
  142. if party_type == "Customer":
  143. party_details["sales_team"] = [
  144. {
  145. "sales_person": d.sales_person,
  146. "allocated_percentage": d.allocated_percentage or None,
  147. "commission_rate": d.commission_rate,
  148. }
  149. for d in party.get("sales_team")
  150. ]
  151. # supplier tax withholding category
  152. if party_type == "Supplier" and party:
  153. party_details["supplier_tds"] = frappe.get_value(
  154. party_type, party.name, "tax_withholding_category"
  155. )
  156. if not party_details.get("tax_category") and pos_profile:
  157. party_details["tax_category"] = frappe.get_value("POS Profile", pos_profile, "tax_category")
  158. return party_details
  159. def set_address_details(
  160. party_details,
  161. party,
  162. party_type,
  163. doctype=None,
  164. company=None,
  165. party_address=None,
  166. company_address=None,
  167. shipping_address=None,
  168. ):
  169. billing_address_field = (
  170. "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
  171. )
  172. party_details[billing_address_field] = party_address or get_default_address(
  173. party_type, party.name
  174. )
  175. if doctype:
  176. party_details.update(
  177. get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
  178. )
  179. # address display
  180. party_details.address_display = get_address_display(party_details[billing_address_field])
  181. # shipping address
  182. if party_type in ["Customer", "Lead"]:
  183. party_details.shipping_address_name = shipping_address or get_party_shipping_address(
  184. party_type, party.name
  185. )
  186. party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
  187. if doctype:
  188. party_details.update(
  189. get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
  190. )
  191. if company_address:
  192. party_details.company_address = company_address
  193. else:
  194. party_details.update(get_company_address(company))
  195. if doctype in SALES_TRANSACTION_TYPES and party_details.company_address:
  196. party_details.update(get_fetch_values(doctype, "company_address", party_details.company_address))
  197. if doctype in PURCHASE_TRANSACTION_TYPES:
  198. if shipping_address:
  199. party_details.update(
  200. shipping_address=shipping_address,
  201. shipping_address_display=get_address_display(shipping_address),
  202. **get_fetch_values(doctype, "shipping_address", shipping_address)
  203. )
  204. if party_details.company_address:
  205. # billing address
  206. party_details.update(
  207. billing_address=party_details.company_address,
  208. billing_address_display=(
  209. party_details.company_address_display or get_address_display(party_details.company_address)
  210. ),
  211. **get_fetch_values(doctype, "billing_address", party_details.company_address)
  212. )
  213. # shipping address - if not already set
  214. if not party_details.shipping_address:
  215. party_details.update(
  216. shipping_address=party_details.billing_address,
  217. shipping_address_display=party_details.billing_address_display,
  218. **get_fetch_values(doctype, "shipping_address", party_details.billing_address)
  219. )
  220. party_address, shipping_address = (
  221. party_details.get(billing_address_field),
  222. party_details.shipping_address_name,
  223. )
  224. party_details["tax_category"] = get_address_tax_category(
  225. party.get("tax_category"),
  226. party_address,
  227. shipping_address if party_type != "Supplier" else party_address,
  228. )
  229. if doctype in TRANSACTION_TYPES:
  230. # required to set correct region
  231. frappe.flags.company = company
  232. get_regional_address_details(party_details, doctype, company)
  233. return party_address, shipping_address
  234. @erpnext.allow_regional
  235. def get_regional_address_details(party_details, doctype, company):
  236. pass
  237. def set_contact_details(party_details, party, party_type):
  238. party_details.contact_person = get_default_contact(party_type, party.name)
  239. if not party_details.contact_person:
  240. party_details.update(
  241. {
  242. "contact_person": None,
  243. "contact_display": None,
  244. "contact_email": None,
  245. "contact_mobile": None,
  246. "contact_phone": None,
  247. "contact_designation": None,
  248. "contact_department": None,
  249. }
  250. )
  251. else:
  252. party_details.update(get_contact_details(party_details.contact_person))
  253. def set_other_values(party_details, party, party_type):
  254. # copy
  255. if party_type == "Customer":
  256. to_copy = ["customer_name", "customer_group", "territory", "language"]
  257. else:
  258. to_copy = ["supplier_name", "supplier_group", "language"]
  259. for f in to_copy:
  260. party_details[f] = party.get(f)
  261. # fields prepended with default in Customer doctype
  262. for f in ["currency"] + (
  263. ["sales_partner", "commission_rate"] if party_type == "Customer" else []
  264. ):
  265. if party.get("default_" + f):
  266. party_details[f] = party.get("default_" + f)
  267. def get_default_price_list(party):
  268. """Return default price list for party (Document object)"""
  269. if party.get("default_price_list"):
  270. return party.default_price_list
  271. if party.doctype == "Customer":
  272. return frappe.db.get_value("Customer Group", party.customer_group, "default_price_list")
  273. def set_price_list(party_details, party, party_type, given_price_list, pos=None):
  274. # price list
  275. price_list = get_permitted_documents("Price List")
  276. # if there is only one permitted document based on user permissions, set it
  277. if price_list and len(price_list) == 1:
  278. price_list = price_list[0]
  279. elif pos and party_type == "Customer":
  280. customer_price_list = frappe.get_value("Customer", party.name, "default_price_list")
  281. if customer_price_list:
  282. price_list = customer_price_list
  283. else:
  284. pos_price_list = frappe.get_value("POS Profile", pos, "selling_price_list")
  285. price_list = pos_price_list or given_price_list
  286. else:
  287. price_list = get_default_price_list(party) or given_price_list
  288. if price_list:
  289. party_details.price_list_currency = frappe.db.get_value(
  290. "Price List", price_list, "currency", cache=True
  291. )
  292. party_details[
  293. "selling_price_list" if party.doctype == "Customer" else "buying_price_list"
  294. ] = price_list
  295. def set_account_and_due_date(
  296. party, account, party_type, company, posting_date, bill_date, doctype
  297. ):
  298. if doctype not in ["POS Invoice", "Sales Invoice", "Purchase Invoice"]:
  299. # not an invoice
  300. return {party_type.lower(): party}
  301. if party:
  302. account = get_party_account(party_type, party, company)
  303. account_fieldname = "debit_to" if party_type == "Customer" else "credit_to"
  304. out = {
  305. party_type.lower(): party,
  306. account_fieldname: account,
  307. "due_date": get_due_date(posting_date, party_type, party, company, bill_date),
  308. }
  309. return out
  310. @frappe.whitelist()
  311. def get_party_account(party_type, party=None, company=None):
  312. """Returns the account for the given `party`.
  313. Will first search in party (Customer / Supplier) record, if not found,
  314. will search in group (Customer Group / Supplier Group),
  315. finally will return default."""
  316. if not company:
  317. frappe.throw(_("Please select a Company"))
  318. if not party and party_type in ["Customer", "Supplier"]:
  319. default_account_name = (
  320. "default_receivable_account" if party_type == "Customer" else "default_payable_account"
  321. )
  322. return frappe.get_cached_value("Company", company, default_account_name)
  323. account = frappe.db.get_value(
  324. "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account"
  325. )
  326. if not account and party_type in ["Customer", "Supplier"]:
  327. party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group"
  328. group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype))
  329. account = frappe.db.get_value(
  330. "Party Account",
  331. {"parenttype": party_group_doctype, "parent": group, "company": company},
  332. "account",
  333. )
  334. if not account and party_type in ["Customer", "Supplier"]:
  335. default_account_name = (
  336. "default_receivable_account" if party_type == "Customer" else "default_payable_account"
  337. )
  338. account = frappe.get_cached_value("Company", company, default_account_name)
  339. existing_gle_currency = get_party_gle_currency(party_type, party, company)
  340. if existing_gle_currency:
  341. if account:
  342. account_currency = frappe.db.get_value("Account", account, "account_currency", cache=True)
  343. if (account and account_currency != existing_gle_currency) or not account:
  344. account = get_party_gle_account(party_type, party, company)
  345. return account
  346. @frappe.whitelist()
  347. def get_party_bank_account(party_type, party):
  348. return frappe.db.get_value(
  349. "Bank Account", {"party_type": party_type, "party": party, "is_default": 1}
  350. )
  351. def get_party_account_currency(party_type, party, company):
  352. def generator():
  353. party_account = get_party_account(party_type, party, company)
  354. return frappe.db.get_value("Account", party_account, "account_currency", cache=True)
  355. return frappe.local_cache("party_account_currency", (party_type, party, company), generator)
  356. def get_party_gle_currency(party_type, party, company):
  357. def generator():
  358. existing_gle_currency = frappe.db.sql(
  359. """select account_currency from `tabGL Entry`
  360. where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
  361. limit 1""",
  362. {"company": company, "party_type": party_type, "party": party},
  363. )
  364. return existing_gle_currency[0][0] if existing_gle_currency else None
  365. return frappe.local_cache(
  366. "party_gle_currency", (party_type, party, company), generator, regenerate_if_none=True
  367. )
  368. def get_party_gle_account(party_type, party, company):
  369. def generator():
  370. existing_gle_account = frappe.db.sql(
  371. """select account from `tabGL Entry`
  372. where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
  373. limit 1""",
  374. {"company": company, "party_type": party_type, "party": party},
  375. )
  376. return existing_gle_account[0][0] if existing_gle_account else None
  377. return frappe.local_cache(
  378. "party_gle_account", (party_type, party, company), generator, regenerate_if_none=True
  379. )
  380. def validate_party_gle_currency(party_type, party, company, party_account_currency=None):
  381. """Validate party account currency with existing GL Entry's currency"""
  382. if not party_account_currency:
  383. party_account_currency = get_party_account_currency(party_type, party, company)
  384. existing_gle_currency = get_party_gle_currency(party_type, party, company)
  385. if existing_gle_currency and party_account_currency != existing_gle_currency:
  386. frappe.throw(
  387. _(
  388. "{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}."
  389. ).format(
  390. frappe.bold(party_type),
  391. frappe.bold(party),
  392. frappe.bold(existing_gle_currency),
  393. frappe.bold(company),
  394. ),
  395. InvalidAccountCurrency,
  396. )
  397. def validate_party_accounts(doc):
  398. from erpnext.controllers.accounts_controller import validate_account_head
  399. companies = []
  400. for account in doc.get("accounts"):
  401. if account.company in companies:
  402. frappe.throw(
  403. _("There can only be 1 Account per Company in {0} {1}").format(doc.doctype, doc.name),
  404. DuplicatePartyAccountError,
  405. )
  406. else:
  407. companies.append(account.company)
  408. party_account_currency = frappe.db.get_value(
  409. "Account", account.account, "account_currency", cache=True
  410. )
  411. if frappe.db.get_default("Company"):
  412. company_default_currency = frappe.get_cached_value(
  413. "Company", frappe.db.get_default("Company"), "default_currency"
  414. )
  415. else:
  416. company_default_currency = frappe.db.get_value("Company", account.company, "default_currency")
  417. validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency)
  418. if doc.get("default_currency") and party_account_currency and company_default_currency:
  419. if (
  420. doc.default_currency != party_account_currency
  421. and doc.default_currency != company_default_currency
  422. ):
  423. frappe.throw(
  424. _(
  425. "Billing currency must be equal to either default company's currency or party account currency"
  426. )
  427. )
  428. # validate if account is mapped for same company
  429. validate_account_head(account.idx, account.account, account.company)
  430. @frappe.whitelist()
  431. def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
  432. """Get due date from `Payment Terms Template`"""
  433. due_date = None
  434. if (bill_date or posting_date) and party:
  435. due_date = bill_date or posting_date
  436. template_name = get_payment_terms_template(party, party_type, company)
  437. if template_name:
  438. due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
  439. "%Y-%m-%d"
  440. )
  441. else:
  442. if party_type == "Supplier":
  443. supplier_group = frappe.get_cached_value(party_type, party, "supplier_group")
  444. template_name = frappe.get_cached_value("Supplier Group", supplier_group, "payment_terms")
  445. if template_name:
  446. due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
  447. "%Y-%m-%d"
  448. )
  449. # If due date is calculated from bill_date, check this condition
  450. if getdate(due_date) < getdate(posting_date):
  451. due_date = posting_date
  452. return due_date
  453. def get_due_date_from_template(template_name, posting_date, bill_date):
  454. """
  455. Inspects all `Payment Term`s from the a `Payment Terms Template` and returns the due
  456. date after considering all the `Payment Term`s requirements.
  457. :param template_name: Name of the `Payment Terms Template`
  458. :return: String representing the calculated due date
  459. """
  460. due_date = getdate(bill_date or posting_date)
  461. template = frappe.get_doc("Payment Terms Template", template_name)
  462. for term in template.terms:
  463. if term.due_date_based_on == "Day(s) after invoice date":
  464. due_date = max(due_date, add_days(due_date, term.credit_days))
  465. elif term.due_date_based_on == "Day(s) after the end of the invoice month":
  466. due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
  467. else:
  468. due_date = max(due_date, get_last_day(add_months(due_date, term.credit_months)))
  469. return due_date
  470. def validate_due_date(
  471. posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
  472. ):
  473. if getdate(due_date) < getdate(posting_date):
  474. frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
  475. else:
  476. if not template_name:
  477. return
  478. default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
  479. "%Y-%m-%d"
  480. )
  481. if not default_due_date:
  482. return
  483. if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
  484. is_credit_controller = (
  485. frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
  486. )
  487. if is_credit_controller:
  488. msgprint(
  489. _("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)").format(
  490. date_diff(due_date, default_due_date)
  491. )
  492. )
  493. else:
  494. frappe.throw(
  495. _("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date))
  496. )
  497. @frappe.whitelist()
  498. def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
  499. addr_tax_category_from = frappe.db.get_single_value(
  500. "Accounts Settings", "determine_address_tax_category_from"
  501. )
  502. if addr_tax_category_from == "Shipping Address":
  503. if shipping_address:
  504. tax_category = frappe.db.get_value("Address", shipping_address, "tax_category") or tax_category
  505. else:
  506. if billing_address:
  507. tax_category = frappe.db.get_value("Address", billing_address, "tax_category") or tax_category
  508. return cstr(tax_category)
  509. @frappe.whitelist()
  510. def set_taxes(
  511. party,
  512. party_type,
  513. posting_date,
  514. company,
  515. customer_group=None,
  516. supplier_group=None,
  517. tax_category=None,
  518. billing_address=None,
  519. shipping_address=None,
  520. use_for_shopping_cart=None,
  521. ):
  522. from erpnext.accounts.doctype.tax_rule.tax_rule import get_party_details, get_tax_template
  523. args = {party_type.lower(): party, "company": company}
  524. if tax_category:
  525. args["tax_category"] = tax_category
  526. if customer_group:
  527. args["customer_group"] = customer_group
  528. if supplier_group:
  529. args["supplier_group"] = supplier_group
  530. if billing_address or shipping_address:
  531. args.update(
  532. get_party_details(
  533. party, party_type, {"billing_address": billing_address, "shipping_address": shipping_address}
  534. )
  535. )
  536. else:
  537. args.update(get_party_details(party, party_type))
  538. if party_type in ("Customer", "Lead"):
  539. args.update({"tax_type": "Sales"})
  540. if party_type == "Lead":
  541. args["customer"] = None
  542. del args["lead"]
  543. else:
  544. args.update({"tax_type": "Purchase"})
  545. if use_for_shopping_cart:
  546. args.update({"use_for_shopping_cart": use_for_shopping_cart})
  547. return get_tax_template(posting_date, args)
  548. @frappe.whitelist()
  549. def get_payment_terms_template(party_name, party_type, company=None):
  550. if party_type not in ("Customer", "Supplier"):
  551. return
  552. template = None
  553. if party_type == "Customer":
  554. customer = frappe.get_cached_value(
  555. "Customer", party_name, fieldname=["payment_terms", "customer_group"], as_dict=1
  556. )
  557. template = customer.payment_terms
  558. if not template and customer.customer_group:
  559. template = frappe.get_cached_value("Customer Group", customer.customer_group, "payment_terms")
  560. else:
  561. supplier = frappe.get_cached_value(
  562. "Supplier", party_name, fieldname=["payment_terms", "supplier_group"], as_dict=1
  563. )
  564. template = supplier.payment_terms
  565. if not template and supplier.supplier_group:
  566. template = frappe.get_cached_value("Supplier Group", supplier.supplier_group, "payment_terms")
  567. if not template and company:
  568. template = frappe.get_cached_value("Company", company, fieldname="payment_terms")
  569. return template
  570. def validate_party_frozen_disabled(party_type, party_name):
  571. if frappe.flags.ignore_party_validation:
  572. return
  573. if party_type and party_name:
  574. if party_type in ("Customer", "Supplier"):
  575. party = frappe.get_cached_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True)
  576. if party.disabled:
  577. frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled)
  578. elif party.get("is_frozen"):
  579. frozen_accounts_modifier = frappe.db.get_single_value(
  580. "Accounts Settings", "frozen_accounts_modifier"
  581. )
  582. if not frozen_accounts_modifier in frappe.get_roles():
  583. frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
  584. elif party_type == "Employee":
  585. if frappe.db.get_value("Employee", party_name, "status") != "Active":
  586. frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
  587. def get_timeline_data(doctype, name):
  588. """returns timeline data for the past one year"""
  589. from frappe.desk.form.load import get_communication_data
  590. out = {}
  591. fields = "creation, count(*)"
  592. after = add_years(None, -1).strftime("%Y-%m-%d")
  593. group_by = "group by Date(creation)"
  594. data = get_communication_data(
  595. doctype,
  596. name,
  597. after=after,
  598. group_by="group by creation",
  599. fields="C.creation as creation, count(C.name)",
  600. as_dict=False,
  601. )
  602. # fetch and append data from Activity Log
  603. data += frappe.db.sql(
  604. """select {fields}
  605. from `tabActivity Log`
  606. where (reference_doctype=%(doctype)s and reference_name=%(name)s)
  607. or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
  608. or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
  609. and status!='Success' and creation > {after}
  610. {group_by} order by creation desc
  611. """.format(
  612. fields=fields, group_by=group_by, after=after
  613. ),
  614. {"doctype": doctype, "name": name},
  615. as_dict=False,
  616. )
  617. timeline_items = dict(data)
  618. for date, count in timeline_items.items():
  619. timestamp = get_timestamp(date)
  620. out.update({timestamp: count})
  621. return out
  622. def get_dashboard_info(party_type, party, loyalty_program=None):
  623. current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)
  624. doctype = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
  625. companies = frappe.get_all(
  626. doctype, filters={"docstatus": 1, party_type.lower(): party}, distinct=1, fields=["company"]
  627. )
  628. company_wise_info = []
  629. company_wise_grand_total = frappe.get_all(
  630. doctype,
  631. filters={
  632. "docstatus": 1,
  633. party_type.lower(): party,
  634. "posting_date": (
  635. "between",
  636. [current_fiscal_year.year_start_date, current_fiscal_year.year_end_date],
  637. ),
  638. },
  639. group_by="company",
  640. fields=[
  641. "company",
  642. "sum(grand_total) as grand_total",
  643. "sum(base_grand_total) as base_grand_total",
  644. ],
  645. )
  646. loyalty_point_details = []
  647. if party_type == "Customer":
  648. loyalty_point_details = frappe._dict(
  649. frappe.get_all(
  650. "Loyalty Point Entry",
  651. filters={
  652. "customer": party,
  653. "expiry_date": (">=", getdate()),
  654. },
  655. group_by="company",
  656. fields=["company", "sum(loyalty_points) as loyalty_points"],
  657. as_list=1,
  658. )
  659. )
  660. company_wise_billing_this_year = frappe._dict()
  661. for d in company_wise_grand_total:
  662. company_wise_billing_this_year.setdefault(
  663. d.company, {"grand_total": d.grand_total, "base_grand_total": d.base_grand_total}
  664. )
  665. company_wise_total_unpaid = frappe._dict(
  666. frappe.db.sql(
  667. """
  668. select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
  669. from `tabGL Entry`
  670. where party_type = %s and party=%s
  671. and is_cancelled = 0
  672. group by company""",
  673. (party_type, party),
  674. )
  675. )
  676. for d in companies:
  677. company_default_currency = frappe.db.get_value("Company", d.company, "default_currency")
  678. party_account_currency = get_party_account_currency(party_type, party, d.company)
  679. if party_account_currency == company_default_currency:
  680. billing_this_year = flt(
  681. company_wise_billing_this_year.get(d.company, {}).get("base_grand_total")
  682. )
  683. else:
  684. billing_this_year = flt(company_wise_billing_this_year.get(d.company, {}).get("grand_total"))
  685. total_unpaid = flt(company_wise_total_unpaid.get(d.company))
  686. if loyalty_point_details:
  687. loyalty_points = loyalty_point_details.get(d.company)
  688. info = {}
  689. info["billing_this_year"] = flt(billing_this_year) if billing_this_year else 0
  690. info["currency"] = party_account_currency
  691. info["total_unpaid"] = flt(total_unpaid) if total_unpaid else 0
  692. info["company"] = d.company
  693. if party_type == "Customer" and loyalty_point_details:
  694. info["loyalty_points"] = loyalty_points
  695. if party_type == "Supplier":
  696. info["total_unpaid"] = -1 * info["total_unpaid"]
  697. company_wise_info.append(info)
  698. return company_wise_info
  699. def get_party_shipping_address(doctype, name):
  700. """
  701. Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
  702. and/or `is_shipping_address = 1`.
  703. It returns an empty string if there is no matching record.
  704. :param doctype: Party Doctype
  705. :param name: Party name
  706. :return: String
  707. """
  708. out = frappe.db.sql(
  709. "SELECT dl.parent "
  710. "from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
  711. "where "
  712. "dl.link_doctype=%s "
  713. "and dl.link_name=%s "
  714. "and dl.parenttype='Address' "
  715. "and ifnull(ta.disabled, 0) = 0 and"
  716. "(ta.address_type='Shipping' or ta.is_shipping_address=1) "
  717. "order by ta.is_shipping_address desc, ta.address_type desc limit 1",
  718. (doctype, name),
  719. )
  720. if out:
  721. return out[0][0]
  722. else:
  723. return ""
  724. def get_partywise_advanced_payment_amount(
  725. party_type, posting_date=None, future_payment=0, company=None
  726. ):
  727. cond = "1=1"
  728. if posting_date:
  729. if future_payment:
  730. cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' " "".format(posting_date)
  731. else:
  732. cond = "posting_date <= '{0}'".format(posting_date)
  733. if company:
  734. cond += "and company = {0}".format(frappe.db.escape(company))
  735. data = frappe.db.sql(
  736. """ SELECT party, sum({0}) as amount
  737. FROM `tabGL Entry`
  738. WHERE
  739. party_type = %s and against_voucher is null
  740. and is_cancelled = 0
  741. and {1} GROUP BY party""".format(
  742. ("credit") if party_type == "Customer" else "debit", cond
  743. ),
  744. party_type,
  745. )
  746. if data:
  747. return frappe._dict(data)
  748. def get_default_contact(doctype, name):
  749. """
  750. Returns default contact for the given doctype and name.
  751. Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
  752. """
  753. out = frappe.db.sql(
  754. """
  755. SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
  756. FROM `tabDynamic Link` dl
  757. INNER JOIN `tabContact` c ON c.name = dl.parent
  758. WHERE
  759. dl.link_doctype=%s AND
  760. dl.link_name=%s AND
  761. dl.parenttype = 'Contact'
  762. ORDER BY is_primary_contact DESC, is_billing_contact DESC
  763. """,
  764. (doctype, name),
  765. )
  766. if out:
  767. try:
  768. return out[0][0]
  769. except Exception:
  770. return None
  771. else:
  772. return None
  773. def add_party_account(party_type, party, company, account):
  774. doc = frappe.get_doc(party_type, party)
  775. account_exists = False
  776. for d in doc.get("accounts"):
  777. if d.account == account:
  778. account_exists = True
  779. if not account_exists:
  780. accounts = {"company": company, "account": account}
  781. doc.append("accounts", accounts)
  782. doc.save()