mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-15 02:43:03 +00:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72cd206286 | ||
|
|
9801745090 | ||
|
|
7866c6e6db | ||
|
|
5812fdb574 | ||
|
|
4c502bcd26 | ||
|
|
714948c867 | ||
|
|
8f3b360f83 | ||
|
|
314086d6c0 | ||
|
|
bcd655a985 | ||
|
|
b0dbdc1439 | ||
|
|
37b0bf257d | ||
|
|
c5a25f44e1 | ||
|
|
8dafa376ab | ||
|
|
74eb8e34da | ||
|
|
5c66fb7631 | ||
|
|
be3b3b2107 | ||
|
|
9ab09fd1d6 | ||
|
|
8a7bdd5a92 | ||
|
|
1d753c92b1 | ||
|
|
54ecc8ebba | ||
|
|
f4edaef481 | ||
|
|
ebbd163903 | ||
|
|
8954b24b22 | ||
|
|
ae92fc7f35 | ||
|
|
0bc3ca02f3 | ||
|
|
d10ba853e6 | ||
|
|
5bcf8315de | ||
|
|
789a798e36 | ||
|
|
ee1169dac7 | ||
|
|
fbef1fdf3a | ||
|
|
4358e1cd46 | ||
|
|
7a287a9153 | ||
|
|
5a49ded5d9 | ||
|
|
71f23acc2b | ||
|
|
1a1f790150 | ||
|
|
3c54e9779b | ||
|
|
db48b7d764 | ||
|
|
83c0899c83 | ||
|
|
e5047ec90a | ||
|
|
d9ab725be4 | ||
|
|
7afaeb0820 | ||
|
|
da2d8b958d | ||
|
|
ba48f82e03 | ||
|
|
8e7e128e81 | ||
|
|
0353569e8b | ||
|
|
665e2f5418 | ||
|
|
dace2b6796 | ||
|
|
712b02593a | ||
|
|
d2a60fd727 | ||
|
|
f924e08b93 | ||
|
|
5c623dae4d | ||
|
|
e2c3d40b57 | ||
|
|
673887455f | ||
|
|
63199e486b | ||
|
|
bf7294cf5c | ||
|
|
8aa06a809a | ||
|
|
72d2d682ae | ||
|
|
21f6ea6f7e | ||
|
|
185f488c51 | ||
|
|
f3006972d5 | ||
|
|
dccc6bc11d | ||
|
|
15d2c89939 | ||
|
|
ca4c8a2a46 | ||
|
|
c320fe541d | ||
|
|
f40a87511e | ||
|
|
4945b94950 | ||
|
|
cb96b61449 | ||
|
|
248c867a2c | ||
|
|
da98ab6f3c | ||
|
|
cd0989e051 | ||
|
|
b20baf894f | ||
|
|
d3cf4f1264 | ||
|
|
2dbe2b63b2 | ||
|
|
edba8f5582 | ||
|
|
89b8d11f9c | ||
|
|
19a33994da | ||
|
|
89349d3ae3 | ||
|
|
f6a31a568a | ||
|
|
4bcbcd29f7 | ||
|
|
740a11263f | ||
|
|
142859f36e | ||
|
|
6534ad082d | ||
|
|
6361ae3495 | ||
|
|
7f75dbf061 | ||
|
|
199d8a44fc | ||
|
|
72b1128467 | ||
|
|
d6cb55ad1a | ||
|
|
c76e34d7de | ||
|
|
33b10faf94 | ||
|
|
d970b001a4 | ||
|
|
d767fb6134 | ||
|
|
6239923340 | ||
|
|
e5a31462fe | ||
|
|
6f39300d43 | ||
|
|
0ca587e018 | ||
|
|
8ffe12ebe4 | ||
|
|
8579af371c | ||
|
|
b5ff9421e1 | ||
|
|
ba8ec17f0b | ||
|
|
44bd3b2601 | ||
|
|
9d40eca428 | ||
|
|
1b6c00e2c7 | ||
|
|
8f2b8afcb7 |
@@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '6.3.2'
|
||||
__version__ = '6.4.6'
|
||||
|
||||
@@ -50,20 +50,20 @@ class Account(Document):
|
||||
def set_root_and_report_type(self):
|
||||
if self.parent_account:
|
||||
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
|
||||
|
||||
|
||||
if par.report_type:
|
||||
self.report_type = par.report_type
|
||||
if par.root_type:
|
||||
self.root_type = par.root_type
|
||||
|
||||
|
||||
if self.is_group:
|
||||
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
|
||||
if db_value:
|
||||
if self.report_type != db_value.report_type:
|
||||
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
||||
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
||||
(self.report_type, self.lft, self.rgt))
|
||||
if self.root_type != db_value.root_type:
|
||||
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
||||
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
||||
(self.root_type, self.lft, self.rgt))
|
||||
|
||||
def validate_root_details(self):
|
||||
@@ -89,11 +89,11 @@ class Account(Document):
|
||||
frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
|
||||
elif account_balance < 0 and self.balance_must_be == "Debit":
|
||||
frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
|
||||
|
||||
|
||||
def validate_account_currency(self):
|
||||
if not self.account_currency:
|
||||
self.account_currency = frappe.db.get_value("Company", self.company, "default_currency")
|
||||
|
||||
|
||||
elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
|
||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
||||
@@ -207,3 +207,16 @@ def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and %s like %s order by name limit %s, %s""" %
|
||||
("%s", searchfield, "%s", "%s", "%s"),
|
||||
(filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
|
||||
|
||||
def get_account_currency(account):
|
||||
"""Helper function to get account currency"""
|
||||
if not account:
|
||||
return
|
||||
def generator():
|
||||
account_currency, company = frappe.db.get_value("Account", account, ["account_currency", "company"])
|
||||
if not account_currency:
|
||||
account_currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
|
||||
return account_currency
|
||||
|
||||
return frappe.local_cache("account_currency", account, generator)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"country_code": "gt",
|
||||
"name": "Cuentas de Guatemala",
|
||||
"is_active": "Yes",
|
||||
"tree": {
|
||||
"Activos": {
|
||||
"Activo Corriente": {
|
||||
"Caja y Bancos": {},
|
||||
"Cuentas por Cobrar": {},
|
||||
"Impuestos por Cobrar": {
|
||||
"IVA por Cobrar": {},
|
||||
"Retenciones de IVA recibidas": {}
|
||||
},
|
||||
"Inventario": {}
|
||||
},
|
||||
"No Corriente": {
|
||||
"Activos Fijos": {},
|
||||
"Cargos Diferidos": {}
|
||||
},
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Pasivos": {
|
||||
"Pasivo Corriente": {
|
||||
"Proveedores": {
|
||||
"Inventario Recibido pero No Cobrado": {
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
}
|
||||
},
|
||||
"Impuestos por Pagar": {},
|
||||
"Sueldos por Liquidar": {},
|
||||
"Prestaciones": {},
|
||||
"Cuentas por Pagar": {},
|
||||
"Otras Cuentas por Pagar": {},
|
||||
"Acreedores": {}
|
||||
},
|
||||
"Pasivo No Corriente": {
|
||||
"Provisión para Indemnizaciones": {},
|
||||
"Acreedores": {}
|
||||
},
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Patrimonio": {
|
||||
"Capital": {},
|
||||
"Utilidades Retenidas": {},
|
||||
"Resultados del Ejercicio": {},
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Costos": {
|
||||
"Costo de Ventas": {},
|
||||
"Costos Incluidos en la Valuación": {
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Gastos": {
|
||||
"Gastos de Personal": {},
|
||||
"Honorarios Profesionales": {},
|
||||
"Servicios Básicos": {},
|
||||
"Alquileres": {},
|
||||
"Seguros": {},
|
||||
"Mantenimiento": {},
|
||||
"Depreciaciones": {},
|
||||
"Gastos Diversos": {},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Ingresos": {
|
||||
"Productos": {},
|
||||
"Servicios": {},
|
||||
"root_type": "Income"
|
||||
},
|
||||
"Otros Gastos y Productos Financieros": {
|
||||
"Otros Ingresos": {
|
||||
"Otros Gastos y Productos Financieros": {
|
||||
"Intereses": {},
|
||||
"Otros Gastos Financieros": {}
|
||||
}
|
||||
},
|
||||
"Otros Gastos": {
|
||||
"Otros Gastos y Productos Financieros": {
|
||||
"Intereses": {},
|
||||
"Otros Gastos Financieros": {}
|
||||
}
|
||||
},
|
||||
"root_type": "Expense"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,38 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2012",
|
||||
"year_end_date": "2012-12-31",
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2012",
|
||||
"year_end_date": "2012-12-31",
|
||||
"year_start_date": "2012-01-01"
|
||||
},
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2013",
|
||||
"year_end_date": "2013-12-31",
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2013",
|
||||
"year_end_date": "2013-12-31",
|
||||
"year_start_date": "2013-01-01"
|
||||
},
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2014",
|
||||
"year_end_date": "2014-12-31",
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2014",
|
||||
"year_end_date": "2014-12-31",
|
||||
"year_start_date": "2014-01-01"
|
||||
},
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2015",
|
||||
"year_end_date": "2015-12-31",
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2015",
|
||||
"year_end_date": "2015-12-31",
|
||||
"year_start_date": "2015-01-01"
|
||||
},
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2016",
|
||||
"year_end_date": "2016-12-31",
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2016",
|
||||
"year_end_date": "2016-12-31",
|
||||
"year_start_date": "2016-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2017",
|
||||
"year_end_date": "2017-12-31",
|
||||
"year_start_date": "2017-01-01"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate
|
||||
from frappe.model.document import Document
|
||||
|
||||
class CustomerFrozen(frappe.ValidationError): pass
|
||||
class InvalidCurrency(frappe.ValidationError): pass
|
||||
class InvalidAccountCurrency(frappe.ValidationError): pass
|
||||
from erpnext.accounts.party import validate_party_gle_currency, get_party_account_currency
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.setup.doctype.company.company import get_company_currency
|
||||
from erpnext.exceptions import InvalidAccountCurrency, CustomerFrozen
|
||||
|
||||
class GLEntry(Document):
|
||||
def validate(self):
|
||||
@@ -101,25 +99,26 @@ class GLEntry(Document):
|
||||
if not frozen_accounts_modifier in frappe.get_roles():
|
||||
if frappe.db.get_value(self.party_type, self.party, "is_frozen"):
|
||||
frappe.throw("{0} {1} is frozen".format(self.party_type, self.party), CustomerFrozen)
|
||||
|
||||
|
||||
def validate_currency(self):
|
||||
company_currency = frappe.db.get_value("Company", self.company, "default_currency")
|
||||
account_currency = frappe.db.get_value("Account", self.account, "account_currency") or company_currency
|
||||
company_currency = get_company_currency(self.company)
|
||||
account_currency = get_account_currency(self.account)
|
||||
|
||||
if not self.account_currency:
|
||||
self.account_currency = company_currency
|
||||
|
||||
if account_currency != self.account_currency:
|
||||
frappe.throw(_("Accounting Entry for {0} can only be made in currency: {1}")
|
||||
.format(self.account, (account_currency or company_currency)), InvalidAccountCurrency)
|
||||
|
||||
|
||||
|
||||
if self.party_type and self.party:
|
||||
party_account_currency = frappe.db.get_value(self.party_type, self.party, "party_account_currency") \
|
||||
or company_currency
|
||||
party_account_currency = get_party_account_currency(self.party_type, self.party, self.company)
|
||||
|
||||
if party_account_currency != self.account_currency:
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
.format(self.party_type, self.party, party_account_currency), InvalidAccountCurrency)
|
||||
.format(self.party_type, self.party, party_account_currency), InvalidAccountCurrency)
|
||||
|
||||
validate_party_gle_currency(self.party_type, self.party, self.company)
|
||||
|
||||
def validate_balance_type(account, adv_adj=False):
|
||||
if not adv_adj and account:
|
||||
@@ -159,7 +158,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
||||
where against_voucher_type=%s and against_voucher=%s
|
||||
and account = %s {0}""".format(party_condition),
|
||||
(against_voucher_type, against_voucher, account))[0][0] or 0.0)
|
||||
|
||||
|
||||
if against_voucher_type == 'Purchase Invoice':
|
||||
bal = -bal
|
||||
elif against_voucher_type == "Journal Entry":
|
||||
|
||||
@@ -335,6 +335,7 @@ frappe.ui.form.on("Journal Entry Account", {
|
||||
party: function(frm, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
if(!d.account && d.party_type && d.party) {
|
||||
if(!frm.doc.company) frappe.throw(__("Please select Company"));
|
||||
return frm.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
|
||||
child: d,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.utils import cstr, flt, fmt_money, formatdate
|
||||
from frappe import msgprint, _, scrub
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.accounts.utils import get_balance_on, get_account_currency
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
self.reference_totals = {}
|
||||
self.reference_types = {}
|
||||
self.reference_parties = {}
|
||||
self.reference_accounts = {}
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if not d.reference_type:
|
||||
@@ -169,8 +169,7 @@ class JournalEntry(AccountsController):
|
||||
self.reference_totals[d.reference_name] = 0.0
|
||||
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
|
||||
self.reference_types[d.reference_name] = d.reference_type
|
||||
if d.party_type and d.party:
|
||||
self.reference_parties[d.reference_name] = [d.party_type, d.party]
|
||||
self.reference_accounts[d.reference_name] = d.account
|
||||
|
||||
against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
|
||||
[scrub(dt) for dt in field_dict.get(d.reference_type)])
|
||||
@@ -196,7 +195,7 @@ class JournalEntry(AccountsController):
|
||||
"""Validate totals, stopped and docstatus for orders"""
|
||||
for reference_name, total in self.reference_totals.iteritems():
|
||||
reference_type = self.reference_types[reference_name]
|
||||
party_type, party = self.reference_parties.get(reference_name)
|
||||
account = self.reference_accounts[reference_name]
|
||||
|
||||
if reference_type in ("Sales Order", "Purchase Order"):
|
||||
order = frappe.db.get_value(reference_type, reference_name,
|
||||
@@ -212,8 +211,8 @@ class JournalEntry(AccountsController):
|
||||
if cstr(order.status) == "Stopped":
|
||||
frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name))
|
||||
|
||||
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency")
|
||||
if party_account_currency == self.company_currency:
|
||||
account_currency = get_account_currency(account)
|
||||
if account_currency == self.company_currency:
|
||||
voucher_total = order.base_grand_total
|
||||
else:
|
||||
voucher_total = order.grand_total
|
||||
@@ -278,9 +277,6 @@ class JournalEntry(AccountsController):
|
||||
if not self.multi_currency:
|
||||
frappe.throw(_("Please check Multi Currency option to allow accounts with other currency"))
|
||||
|
||||
if len(alternate_currency) > 1:
|
||||
frappe.throw(_("Only one alternate currency can be used in a single Journal Entry"))
|
||||
|
||||
self.set_exchange_rate()
|
||||
|
||||
for d in self.get("accounts"):
|
||||
@@ -609,8 +605,8 @@ def get_payment_entry_from_sales_order(sales_order):
|
||||
jv = get_payment_entry(so)
|
||||
jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name)
|
||||
|
||||
party_account = get_party_account(so.company, so.customer, "Customer")
|
||||
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
|
||||
party_account = get_party_account("Customer", so.customer, so.company)
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
|
||||
exchange_rate = get_exchange_rate(party_account, party_account_currency, so.company)
|
||||
|
||||
@@ -660,8 +656,8 @@ def get_payment_entry_from_purchase_order(purchase_order):
|
||||
jv = get_payment_entry(po)
|
||||
jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name)
|
||||
|
||||
party_account = get_party_account(po.company, po.supplier, "Supplier")
|
||||
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
|
||||
party_account = get_party_account("Supplier", po.supplier, po.company)
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
|
||||
exchange_rate = get_exchange_rate(party_account, party_account_currency, po.company)
|
||||
|
||||
@@ -779,7 +775,7 @@ def get_party_account_and_balance(company, party_type, party):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
from erpnext.accounts.party import get_party_account
|
||||
account = get_party_account(company, party, party_type)
|
||||
account = get_party_account(party_type, party, company)
|
||||
|
||||
account_balance = get_balance_on(account=account)
|
||||
party_balance = get_balance_on(party_type=party_type, party=party)
|
||||
@@ -826,17 +822,20 @@ def get_exchange_rate(account, account_currency, company,
|
||||
if account_currency != company_currency:
|
||||
if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name:
|
||||
exchange_rate = frappe.db.get_value(reference_type, reference_name, "conversion_rate")
|
||||
elif account_details.account_type == "Bank" and \
|
||||
|
||||
elif account_details and account_details.account_type == "Bank" and \
|
||||
((account_details.root_type == "Asset" and flt(credit) > 0) or
|
||||
(account_details.root_type == "Liability" and debit)):
|
||||
exchange_rate = get_average_exchange_rate(account)
|
||||
|
||||
if not exchange_rate:
|
||||
if not exchange_rate and account_currency:
|
||||
exchange_rate = get_exchange_rate(account_currency, company_currency)
|
||||
|
||||
else:
|
||||
exchange_rate = 1
|
||||
|
||||
return exchange_rate
|
||||
# don't return None or 0 as it is multipled with a value and that value could be lost
|
||||
return exchange_rate or 1
|
||||
|
||||
def get_average_exchange_rate(account):
|
||||
exchange_rate = 0
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import unittest, frappe
|
||||
from frappe.utils import flt
|
||||
from erpnext.accounts.utils import get_actual_expense, BudgetError, get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
|
||||
|
||||
class TestJournalEntry(unittest.TestCase):
|
||||
@@ -166,15 +167,15 @@ class TestJournalEntry(unittest.TestCase):
|
||||
existing_expense = self.get_actual_expense(posting_date)
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
|
||||
|
||||
|
||||
def test_multi_currency(self):
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"_Test Bank - _TC", 100, exchange_rate=50, save=False)
|
||||
|
||||
|
||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||
jv.submit()
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""", jv.name, as_dict=1)
|
||||
@@ -197,12 +198,10 @@ class TestJournalEntry(unittest.TestCase):
|
||||
"credit_in_account_currency": 5000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[gle.account][field], gle[field])
|
||||
|
||||
|
||||
|
||||
# cancel
|
||||
jv.cancel()
|
||||
@@ -212,6 +211,40 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||
# create jv in USD
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"_Test Receivable USD - _TC", 100, save=False)
|
||||
|
||||
jv.accounts[1].update({
|
||||
"party_type": "Customer",
|
||||
"party": "_Test Customer USD"
|
||||
})
|
||||
|
||||
jv.submit()
|
||||
|
||||
# create jv in USD, but account currency in INR
|
||||
jv = make_journal_entry("_Test Bank - _TC",
|
||||
"_Test Receivable - _TC", 100, save=False)
|
||||
|
||||
jv.accounts[1].update({
|
||||
"party_type": "Customer",
|
||||
"party": "_Test Customer USD"
|
||||
})
|
||||
|
||||
self.assertRaises(InvalidAccountCurrency, jv.submit)
|
||||
|
||||
# back in USD
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"_Test Receivable USD - _TC", 100, save=False)
|
||||
|
||||
jv.accounts[1].update({
|
||||
"party_type": "Customer",
|
||||
"party": "_Test Customer USD"
|
||||
})
|
||||
|
||||
jv.submit()
|
||||
|
||||
def make_journal_entry(account1, account2, amount, cost_center=None, exchange_rate=1, save=True, submit=False):
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.posting_date = "2013-02-14"
|
||||
@@ -231,7 +264,7 @@ def make_journal_entry(account1, account2, amount, cost_center=None, exchange_ra
|
||||
"cost_center": cost_center,
|
||||
"credit_in_account_currency": amount if amount > 0 else 0,
|
||||
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
|
||||
exchange_rate: exchange_rate
|
||||
"exchange_rate": exchange_rate
|
||||
}
|
||||
])
|
||||
if save or submit:
|
||||
|
||||
@@ -74,21 +74,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
doc: me.frm.doc,
|
||||
method: 'get_unreconciled_entries',
|
||||
callback: function(r, rt) {
|
||||
var invoices = [];
|
||||
|
||||
$.each(me.frm.doc.invoices || [], function(i, row) {
|
||||
if (row.invoice_number && !inList(invoices, row.invoice_number))
|
||||
invoices.push(row.invoice_type + " | " + row.invoice_number);
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
|
||||
me.frm.doc.name).options = invoices.join("\n");
|
||||
|
||||
$.each(me.frm.doc.payments || [], function(i, p) {
|
||||
if(!inList(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
||||
});
|
||||
|
||||
refresh_field("payments");
|
||||
me.set_invoice_options();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,8 +84,29 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
var me = this;
|
||||
return this.frm.call({
|
||||
doc: me.frm.doc,
|
||||
method: 'reconcile'
|
||||
method: 'reconcile',
|
||||
callback: function(r, rt) {
|
||||
me.set_invoice_options();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_invoice_options: function() {
|
||||
var invoices = [];
|
||||
|
||||
$.each(me.frm.doc.invoices || [], function(i, row) {
|
||||
if (row.invoice_number && !inList(invoices, row.invoice_number))
|
||||
invoices.push(row.invoice_type + " | " + row.invoice_number);
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
|
||||
me.frm.doc.name).options = invoices.join("\n");
|
||||
|
||||
$.each(me.frm.doc.payments || [], function(i, p) {
|
||||
if(!inList(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
||||
});
|
||||
|
||||
refresh_field("payments");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt
|
||||
|
||||
from frappe import msgprint, _
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PaymentReconciliation(Document):
|
||||
@@ -17,7 +14,8 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def get_jv_entries(self):
|
||||
self.check_mandatory_to_fetch()
|
||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
||||
dr_or_cr = "credit_in_account_currency" if self.party_type == "Customer" \
|
||||
else "debit_in_account_currency"
|
||||
|
||||
cond = self.check_condition(dr_or_cr)
|
||||
|
||||
@@ -68,9 +66,9 @@ class PaymentReconciliation(Document):
|
||||
def get_invoice_entries(self):
|
||||
#Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
|
||||
non_reconciled_invoices = []
|
||||
dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
|
||||
dr_or_cr = "debit_in_account_currency" if self.party_type == "Customer" else "credit_in_account_currency"
|
||||
cond = self.check_condition(dr_or_cr)
|
||||
|
||||
|
||||
invoice_list = frappe.db.sql("""
|
||||
select
|
||||
voucher_no, voucher_type, posting_date,
|
||||
@@ -106,13 +104,15 @@ class PaymentReconciliation(Document):
|
||||
and account = %(account)s and {0} > 0
|
||||
and against_voucher_type = %(against_voucher_type)s
|
||||
and ifnull(against_voucher, '') = %(against_voucher)s
|
||||
""".format("credit" if self.party_type == "Customer" else "debit"), {
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"account": self.receivable_payable_account,
|
||||
"against_voucher_type": d.voucher_type,
|
||||
"against_voucher": d.voucher_no
|
||||
})
|
||||
""".format("credit_in_account_currency" if self.party_type == "Customer"
|
||||
else "debit_in_account_currency"), {
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"account": self.receivable_payable_account,
|
||||
"against_voucher_type": d.voucher_type,
|
||||
"against_voucher": d.voucher_no
|
||||
}
|
||||
)
|
||||
|
||||
payment_amount = payment_amount[0][0] if payment_amount else 0
|
||||
|
||||
@@ -141,12 +141,14 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def reconcile(self, args):
|
||||
for e in self.get('payments'):
|
||||
if " | " in e.invoice_number:
|
||||
e.invoice_type = None
|
||||
if e.invoice_number and " | " in e.invoice_number:
|
||||
e.invoice_type, e.invoice_number = e.invoice_number.split(" | ")
|
||||
|
||||
self.get_invoice_entries()
|
||||
self.validate_invoice()
|
||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
||||
dr_or_cr = "credit_in_account_currency" if self.party_type == "Customer" \
|
||||
else "debit_in_account_currency"
|
||||
lst = []
|
||||
for e in self.get('payments'):
|
||||
if e.invoice_number and e.allocated_amount:
|
||||
|
||||
@@ -141,7 +141,10 @@ frappe.ui.form.on("Payment Tool", "get_outstanding_vouchers", function(frm) {
|
||||
c.against_voucher_no = d.voucher_no;
|
||||
c.total_amount = d.invoice_amount;
|
||||
c.outstanding_amount = d.outstanding_amount;
|
||||
c.payment_amount = d.outstanding_amount;
|
||||
|
||||
if (frm.doc.set_payment_amount) {
|
||||
c.payment_amount = d.outstanding_amount;
|
||||
}
|
||||
});
|
||||
}
|
||||
refresh_field("vouchers");
|
||||
|
||||
@@ -185,6 +185,28 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "set_payment_amount",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Set Payment Amount = Outstanding Amount",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@@ -474,7 +496,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-08-31 18:58:21.813054",
|
||||
"modified": "2015-10-01 09:43:24.199025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Tool",
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe import _, scrub
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
import json
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
class PaymentTool(Document):
|
||||
def make_journal_entry(self):
|
||||
@@ -59,7 +60,7 @@ def get_outstanding_vouchers(args):
|
||||
|
||||
args = json.loads(args)
|
||||
|
||||
party_account_currency = frappe.db.get_value("Account", args.get("party_account"), "account_currency")
|
||||
party_account_currency = get_account_currency(args.get("party_account"))
|
||||
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
|
||||
|
||||
if args.get("party_type") == "Customer" and args.get("received_or_paid") == "Received":
|
||||
@@ -112,7 +113,7 @@ def get_orders_to_be_billed(party_type, party, party_account_currency, company_c
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_against_voucher_amount(against_voucher_type, against_voucher_no, party_account, company):
|
||||
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
company_currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ import frappe.defaults
|
||||
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@@ -66,7 +67,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
if not self.credit_to:
|
||||
self.credit_to = get_party_account(self.company, self.supplier, "Supplier")
|
||||
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
|
||||
if not self.due_date:
|
||||
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company)
|
||||
|
||||
@@ -91,7 +92,7 @@ class PurchaseInvoice(BuyingController):
|
||||
throw(_("Conversion rate cannot be 0 or 1"))
|
||||
|
||||
def validate_credit_to_acc(self):
|
||||
account = frappe.db.get_value("Account", self.credit_to,
|
||||
account = frappe.db.get_value("Account", self.credit_to,
|
||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
@@ -99,7 +100,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if self.supplier and account.account_type != "Payable":
|
||||
frappe.throw(_("Credit To account must be a Payable account"))
|
||||
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
def check_for_stopped_status(self):
|
||||
@@ -251,7 +252,7 @@ class PurchaseInvoice(BuyingController):
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
gl_entries = []
|
||||
|
||||
|
||||
# parent's gl entry
|
||||
if self.base_grand_total:
|
||||
gl_entries.append(
|
||||
@@ -272,10 +273,10 @@ class PurchaseInvoice(BuyingController):
|
||||
valuation_tax = {}
|
||||
for tax in self.get("taxes"):
|
||||
if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
|
||||
account_currency = frappe.db.get_value("Account", tax.account_head, "account_currency")
|
||||
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
|
||||
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
@@ -301,7 +302,7 @@ class PurchaseInvoice(BuyingController):
|
||||
stock_items = self.get_stock_items()
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount):
|
||||
account_currency = frappe.db.get_value("Account", item.expense_account, "account_currency")
|
||||
account_currency = get_account_currency(item.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
@@ -363,8 +364,8 @@ class PurchaseInvoice(BuyingController):
|
||||
# writeoff account includes petty difference in the invoice amount
|
||||
# and the amount that is paid
|
||||
if self.write_off_account and flt(self.write_off_amount):
|
||||
write_off_account_currency = frappe.db.get_value("Account", self.write_off_account, "account_currency")
|
||||
|
||||
write_off_account_currency = get_account_currency(self.write_off_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.credit_to,
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.utils import cint
|
||||
import frappe.defaults
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
|
||||
test_records as pr_test_records
|
||||
from erpnext.controllers.accounts_controller import InvalidCurrency
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
|
||||
test_dependencies = ["Item", "Cost Center"]
|
||||
test_ignore = ["Serial No"]
|
||||
@@ -219,7 +219,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_type='Purchase Invoice'
|
||||
where reference_type='Purchase Invoice'
|
||||
and reference_name=%s and debit_in_account_currency=300""", pi.name))
|
||||
|
||||
self.assertEqual(pi.outstanding_amount, 1212.30)
|
||||
@@ -237,17 +237,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
existing_purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0))
|
||||
from `tabPurchase Invoice Item` where project_name = '_Test Project' and docstatus=1""")
|
||||
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
||||
|
||||
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project_name="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000)
|
||||
|
||||
pi1 = make_purchase_invoice(qty=10, project_name="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
existing_purchase_cost + 15500)
|
||||
|
||||
pi1.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000)
|
||||
|
||||
pi.cancel()
|
||||
@@ -278,14 +278,14 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def test_multi_currency_gle(self):
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
||||
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
order by account asc""", pi.name, as_dict=1)
|
||||
@@ -308,16 +308,16 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
"credit_in_account_currency": 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[gle.account][field], gle[field])
|
||||
|
||||
|
||||
|
||||
|
||||
# Check for valid currency
|
||||
pi1 = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
||||
do_not_save=True)
|
||||
|
||||
|
||||
self.assertRaises(InvalidCurrency, pi1.save)
|
||||
|
||||
# cancel
|
||||
|
||||
@@ -671,7 +671,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -1002,7 +1002,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "taxes",
|
||||
@@ -2951,7 +2951,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-23 09:52:09.675668",
|
||||
"modified": "2015-09-30 08:52:53.679566",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -11,6 +11,7 @@ from erpnext.controllers.stock_controller import update_gl_entries_after
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@@ -186,7 +187,7 @@ class SalesInvoice(SellingController):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
self.debit_to = get_party_account(self.company, self.customer, "Customer")
|
||||
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||
if not self.due_date and self.customer:
|
||||
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
|
||||
|
||||
@@ -206,8 +207,8 @@ class SalesInvoice(SellingController):
|
||||
def validate_time_logs_are_submitted(self):
|
||||
for d in self.get("items"):
|
||||
if d.time_log_batch:
|
||||
status = frappe.db.get_value("Time Log Batch", d.time_log_batch, "status")
|
||||
if status!="Submitted":
|
||||
docstatus = frappe.db.get_value("Time Log Batch", d.time_log_batch, "docstatus")
|
||||
if docstatus!=1:
|
||||
frappe.throw(_("Time Log Batch {0} must be 'Submitted'").format(d.time_log_batch))
|
||||
|
||||
def set_pos_fields(self, for_validate=False):
|
||||
@@ -531,7 +532,7 @@ class SalesInvoice(SellingController):
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
for tax in self.get("taxes"):
|
||||
if flt(tax.base_tax_amount_after_discount_amount):
|
||||
account_currency = frappe.db.get_value("Account", tax.account_head, "account_currency")
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
@@ -547,7 +548,7 @@ class SalesInvoice(SellingController):
|
||||
# income account gl entries
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount):
|
||||
account_currency = frappe.db.get_value("Account", item.income_account, "account_currency")
|
||||
account_currency = get_account_currency(item.income_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.income_account,
|
||||
@@ -566,7 +567,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_pos_gl_entries(self, gl_entries):
|
||||
if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:
|
||||
bank_account_currency = frappe.db.get_value("Account", self.cash_bank_account, "account_currency")
|
||||
bank_account_currency = get_account_currency(self.cash_bank_account)
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@@ -594,7 +595,7 @@ class SalesInvoice(SellingController):
|
||||
def make_write_off_gl_entry(self, gl_entries):
|
||||
# write off entries, applicable if only pos
|
||||
if self.write_off_account and self.write_off_amount:
|
||||
write_off_account_currency = frappe.db.get_value("Account", self.write_off_account, "account_currency")
|
||||
write_off_account_currency = get_account_currency(self.write_off_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
|
||||
@@ -7,8 +7,7 @@ import unittest, copy
|
||||
from frappe.utils import nowdate, add_days, flt
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
from erpnext.controllers.accounts_controller import InvalidCurrency
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import InvalidAccountCurrency
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@@ -842,13 +841,13 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEquals(si.total_taxes_and_charges, 234.44)
|
||||
self.assertEquals(si.base_grand_total, 859.44)
|
||||
self.assertEquals(si.grand_total, 859.44)
|
||||
|
||||
|
||||
def test_multi_currency_gle(self):
|
||||
set_perpetual_inventory(0)
|
||||
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
|
||||
order by account asc""", si.name, as_dict=1)
|
||||
@@ -871,7 +870,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"credit_in_account_currency": 5000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[gle.account][field], gle[field])
|
||||
@@ -883,38 +882,38 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
|
||||
def test_invalid_currency(self):
|
||||
# Customer currency = USD
|
||||
|
||||
|
||||
# Transaction currency cannot be INR
|
||||
si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
do_not_save=True)
|
||||
|
||||
|
||||
self.assertRaises(InvalidCurrency, si1.save)
|
||||
|
||||
|
||||
# Transaction currency cannot be EUR
|
||||
si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
currency="EUR", conversion_rate=80, do_not_save=True)
|
||||
|
||||
|
||||
self.assertRaises(InvalidCurrency, si2.save)
|
||||
|
||||
|
||||
# Transaction currency only allowed in USD
|
||||
si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
|
||||
|
||||
# Party Account currency must be in USD, as there is existing GLE with USD
|
||||
si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
|
||||
si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
|
||||
currency="USD", conversion_rate=50, do_not_submit=True)
|
||||
|
||||
|
||||
self.assertRaises(InvalidAccountCurrency, si4.submit)
|
||||
|
||||
|
||||
# Party Account currency must be in USD, force customer currency as there is no GLE
|
||||
|
||||
|
||||
si3.cancel()
|
||||
si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
|
||||
si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
|
||||
currency="USD", conversion_rate=50, do_not_submit=True)
|
||||
|
||||
|
||||
self.assertRaises(InvalidAccountCurrency, si5.submit)
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
|
||||
@@ -10,9 +10,9 @@ from frappe.defaults import get_user_permissions
|
||||
from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff
|
||||
from erpnext.utilities.doctype.address.address import get_address_display
|
||||
from erpnext.utilities.doctype.contact.contact import get_contact_details
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
|
||||
class InvalidCurrency(frappe.ValidationError): pass
|
||||
class InvalidAccountCurrency(frappe.ValidationError): pass
|
||||
class DuplicatePartyAccountError(frappe.ValidationError): pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(party=None, account=None, party_type="Customer", company=None,
|
||||
@@ -142,7 +142,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
|
||||
}
|
||||
|
||||
if party:
|
||||
account = get_party_account(company, party, party_type)
|
||||
account = get_party_account(party_type, party, company)
|
||||
|
||||
account_fieldname = "debit_to" if party_type=="Customer" else "credit_to"
|
||||
|
||||
@@ -153,44 +153,6 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
|
||||
}
|
||||
return out
|
||||
|
||||
def validate_accounting_currency(party):
|
||||
party_account_currency_in_db = frappe.db.get_value(party.doctype, party.name, "party_account_currency")
|
||||
if party_account_currency_in_db != party.party_account_currency:
|
||||
existing_gle = frappe.db.get_value("GL Entry", {"party_type": party.doctype,
|
||||
"party": party.name}, ["name", "account_currency"], as_dict=1)
|
||||
if existing_gle:
|
||||
if party_account_currency_in_db:
|
||||
frappe.throw(_("Accounting Currency cannot be changed, as GL Entry exists for this {0}")
|
||||
.format(party.doctype), InvalidCurrency)
|
||||
else:
|
||||
party.party_account_currency = existing_gle.account_currency
|
||||
|
||||
|
||||
def validate_party_account(party):
|
||||
company_currency = get_company_currency()
|
||||
if party.party_account_currency:
|
||||
companies_with_different_currency = []
|
||||
for company, currency in company_currency.items():
|
||||
if currency != party.party_account_currency:
|
||||
companies_with_different_currency.append(company)
|
||||
|
||||
for d in party.get("accounts"):
|
||||
if d.company in companies_with_different_currency:
|
||||
companies_with_different_currency.remove(d.company)
|
||||
|
||||
selected_account_currency = frappe.db.get_value("Account", d.account, "account_currency")
|
||||
if selected_account_currency != party.party_account_currency:
|
||||
frappe.throw(_("Account {0} is invalid, account currency must be {1}")
|
||||
.format(d.account, selected_account_currency), InvalidAccountCurrency)
|
||||
|
||||
if companies_with_different_currency:
|
||||
frappe.msgprint(_("Please mention Default {0} Account for the following companies, as accounting currency is different from company's default currency: {1}")
|
||||
.format(
|
||||
"Receivable" if party.doctype=="Customer" else "Payable",
|
||||
"\n" + "\n".join(companies_with_different_currency)
|
||||
)
|
||||
)
|
||||
|
||||
def get_company_currency():
|
||||
company_currency = frappe._dict()
|
||||
for d in frappe.get_all("Company", fields=["name", "default_currency"]):
|
||||
@@ -199,13 +161,13 @@ def get_company_currency():
|
||||
return company_currency
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account(company, party, party_type):
|
||||
def get_party_account(party_type, party, company):
|
||||
"""Returns the account for the given `party`.
|
||||
Will first search in party (Customer / Supplier) record, if not found,
|
||||
will search in group (Customer Group / Supplier Type),
|
||||
finally will return default."""
|
||||
if not company:
|
||||
frappe.throw(_("Please select company first."))
|
||||
frappe.throw(_("Please select a Company"))
|
||||
|
||||
if party:
|
||||
account = frappe.db.get_value("Party Account",
|
||||
@@ -223,6 +185,42 @@ def get_party_account(company, party, party_type):
|
||||
|
||||
return account
|
||||
|
||||
def get_party_account_currency(party_type, party, company):
|
||||
def generator():
|
||||
party_account = get_party_account(party_type, party, company)
|
||||
return frappe.db.get_value("Account", party_account, "account_currency")
|
||||
|
||||
return frappe.local_cache("party_account_currency", (party_type, party, company), generator)
|
||||
|
||||
def get_party_gle_currency(party_type, party, company):
|
||||
def generator():
|
||||
existing_gle_currency = frappe.db.sql("""select account_currency from `tabGL Entry`
|
||||
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
|
||||
limit 1""", { "company": company, "party_type": party_type, "party": party })
|
||||
|
||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||
|
||||
return frappe.local_cache("party_gle_currency", (party_type, party, company), generator)
|
||||
|
||||
def validate_party_gle_currency(party_type, party, company):
|
||||
"""Validate party account currency with existing GL Entry's currency"""
|
||||
party_account_currency = get_party_account_currency(party_type, party, company)
|
||||
existing_gle_currency = get_party_gle_currency(party_type, party, company)
|
||||
|
||||
if existing_gle_currency and party_account_currency != existing_gle_currency:
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
.format(party_type, party, existing_gle_currency), InvalidAccountCurrency)
|
||||
|
||||
def validate_party_accounts(doc):
|
||||
companies = []
|
||||
|
||||
for account in doc.get("accounts"):
|
||||
if account.company in companies:
|
||||
frappe.throw(_("There can only be 1 Account per Company in {0} {1}").format(doc.doctype, doc.name),
|
||||
DuplicatePartyAccountError)
|
||||
else:
|
||||
companies.append(account.company)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_due_date(posting_date, party_type, party, company):
|
||||
"""Set Due Date = Posting Date + Credit Days"""
|
||||
@@ -267,6 +265,9 @@ def validate_due_date(posting_date, due_date, party_type, party, company):
|
||||
frappe.throw(_("Due Date cannot be before Posting Date"))
|
||||
else:
|
||||
default_due_date = get_due_date(posting_date, party_type, party, company)
|
||||
if not default_due_date:
|
||||
return
|
||||
|
||||
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
|
||||
is_credit_controller = frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
|
||||
if is_credit_controller:
|
||||
|
||||
@@ -40,6 +40,13 @@ class ReceivablePayableReport(object):
|
||||
|
||||
columns += [_("Age (Days)") + "::80"]
|
||||
|
||||
if not "range1" in self.filters:
|
||||
self.filters["range1"] = "30"
|
||||
if not "range2" in self.filters:
|
||||
self.filters["range2"] = "60"
|
||||
if not "range3" in self.filters:
|
||||
self.filters["range3"] = "90"
|
||||
|
||||
for label in ("0-{range1}".format(**self.filters),
|
||||
"{range1}-{range2}".format(**self.filters),
|
||||
"{range2}-{range3}".format(**self.filters),
|
||||
@@ -75,9 +82,9 @@ class ReceivablePayableReport(object):
|
||||
voucher_details = self.get_voucher_details(args.get("party_type"))
|
||||
|
||||
future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type"))
|
||||
|
||||
|
||||
company_currency = frappe.db.get_value("Company", self.filters.get("company"), "default_currency")
|
||||
|
||||
|
||||
data = []
|
||||
for gle in self.get_entries_till(self.filters.report_date, args.get("party_type")):
|
||||
if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers):
|
||||
@@ -117,7 +124,7 @@ class ReceivablePayableReport(object):
|
||||
row += [self.get_territory(gle.party)]
|
||||
if args.get("party_type") == "Supplier":
|
||||
row += [self.get_supplier_type(gle.party)]
|
||||
|
||||
|
||||
if self.filters.get(scrub(args.get("party_type"))):
|
||||
row.append(gle.account_currency)
|
||||
else:
|
||||
@@ -209,7 +216,8 @@ class ReceivablePayableReport(object):
|
||||
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
|
||||
voucher_type, voucher_no, against_voucher_type, against_voucher, account_currency, remarks, {0}
|
||||
from `tabGL Entry`
|
||||
where docstatus < 2 and party_type=%s {1} order by posting_date, party"""
|
||||
where docstatus < 2 and party_type=%s and ifnull(party, '') != '' {1}
|
||||
order by posting_date, party"""
|
||||
.format(select_fields, conditions), values, as_dict=True)
|
||||
|
||||
return self.gl_entries
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt, getdate, cstr
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
def execute(filters=None):
|
||||
account_details = {}
|
||||
@@ -12,10 +13,10 @@ def execute(filters=None):
|
||||
account_details.setdefault(acc.name, acc)
|
||||
|
||||
validate_filters(filters, account_details)
|
||||
|
||||
|
||||
validate_party(filters)
|
||||
|
||||
filters = set_account_currency(filters)
|
||||
|
||||
filters = set_account_currency(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
|
||||
@@ -46,49 +47,49 @@ def validate_party(filters):
|
||||
frappe.throw(_("To filter based on Party, select Party Type first"))
|
||||
elif not frappe.db.exists(party_type, party):
|
||||
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
|
||||
|
||||
|
||||
def set_account_currency(filters):
|
||||
if not (filters.get("account") or filters.get("party")):
|
||||
return filters
|
||||
else:
|
||||
filters["company_currency"] = frappe.db.get_value("Company", filters.company, "default_currency")
|
||||
account_currency = None
|
||||
|
||||
|
||||
if filters.get("account"):
|
||||
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
|
||||
account_currency = get_account_currency(filters.account)
|
||||
elif filters.get("party"):
|
||||
gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type,
|
||||
gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type,
|
||||
"party": filters.party, "company": filters.company}, "account_currency")
|
||||
if gle_currency:
|
||||
account_currency = gle_currency
|
||||
else:
|
||||
account_currency = frappe.db.get_value(filters.party_type, filters.party, "default_currency")
|
||||
|
||||
|
||||
filters["account_currency"] = account_currency or filters.company_currency
|
||||
|
||||
|
||||
if filters.account_currency != filters.company_currency:
|
||||
filters["show_in_account_currency"] = 1
|
||||
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
_("Posting Date") + ":Date:90", _("Account") + ":Link/Account:200",
|
||||
_("Debit") + ":Float:100", _("Credit") + ":Float:100"
|
||||
]
|
||||
|
||||
|
||||
if filters.get("show_in_account_currency"):
|
||||
columns += [
|
||||
_("Debit") + " (" + filters.account_currency + ")" + ":Float:100",
|
||||
_("Debit") + " (" + filters.account_currency + ")" + ":Float:100",
|
||||
_("Credit") + " (" + filters.account_currency + ")" + ":Float:100"
|
||||
]
|
||||
|
||||
|
||||
columns += [
|
||||
_("Voucher Type") + "::120", _("Voucher No") + ":Dynamic Link/Voucher Type:160",
|
||||
_("Against Account") + "::120", _("Party Type") + "::80", _("Party") + "::150",
|
||||
_("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"
|
||||
]
|
||||
|
||||
|
||||
return columns
|
||||
|
||||
def get_result(filters, account_details):
|
||||
@@ -101,21 +102,21 @@ def get_result(filters, account_details):
|
||||
return result
|
||||
|
||||
def get_gl_entries(filters):
|
||||
select_fields = """, sum(ifnull(debit_in_account_currency, 0)) as debit_in_account_currency,
|
||||
select_fields = """, sum(ifnull(debit_in_account_currency, 0)) as debit_in_account_currency,
|
||||
sum(ifnull(credit_in_account_currency, 0)) as credit_in_account_currency""" \
|
||||
if filters.get("show_in_account_currency") else ""
|
||||
|
||||
|
||||
group_by_condition = "group by voucher_type, voucher_no, account, cost_center" \
|
||||
if filters.get("group_by_voucher") else "group by name"
|
||||
|
||||
gl_entries = frappe.db.sql("""select posting_date, account, party_type, party,
|
||||
sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit,
|
||||
sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit,
|
||||
voucher_type, voucher_no, cost_center, remarks, against, is_opening {select_fields}
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions}
|
||||
{group_by_condition}
|
||||
order by posting_date, account"""\
|
||||
.format(select_fields=select_fields, conditions=get_conditions(filters),
|
||||
.format(select_fields=select_fields, conditions=get_conditions(filters),
|
||||
group_by_condition=group_by_condition), filters, as_dict=1)
|
||||
|
||||
return gl_entries
|
||||
@@ -135,7 +136,7 @@ def get_conditions(filters):
|
||||
|
||||
if filters.get("party"):
|
||||
conditions.append("party=%(party)s")
|
||||
|
||||
|
||||
if not (filters.get("account") or filters.get("party") or filters.get("group_by_account")):
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
|
||||
@@ -156,33 +157,38 @@ def get_data_with_opening_closing(filters, account_details, gl_entries):
|
||||
if filters.get("account") or filters.get("party"):
|
||||
data += [get_balance_row(_("Opening"), opening, opening_in_account_currency), {}]
|
||||
|
||||
for acc, acc_dict in gle_map.items():
|
||||
if acc_dict.entries:
|
||||
# Opening for individual ledger, if grouped by account
|
||||
if filters.get("group_by_account"):
|
||||
data.append(get_balance_row(_("Opening"), acc_dict.opening,
|
||||
if filters.get("group_by_account"):
|
||||
for acc, acc_dict in gle_map.items():
|
||||
if acc_dict.entries:
|
||||
# Opening for individual ledger, if grouped by account
|
||||
data.append(get_balance_row(_("Opening"), acc_dict.opening,
|
||||
acc_dict.opening_in_account_currency))
|
||||
|
||||
data += acc_dict.entries
|
||||
data += acc_dict.entries
|
||||
|
||||
# Totals and closing for individual ledger, if grouped by account
|
||||
if filters.get("group_by_account"):
|
||||
# Totals and closing for individual ledger, if grouped by account
|
||||
account_closing = acc_dict.opening + acc_dict.total_debit - acc_dict.total_credit
|
||||
account_closing_in_account_currency = acc_dict.opening_in_account_currency \
|
||||
+ acc_dict.total_debit_in_account_currency - acc_dict.total_credit_in_account_currency
|
||||
|
||||
|
||||
data += [{"account": "'" + _("Totals") + "'", "debit": acc_dict.total_debit,
|
||||
"credit": acc_dict.total_credit},
|
||||
get_balance_row(_("Closing (Opening + Totals)"),
|
||||
account_closing, account_closing_in_account_currency), {}]
|
||||
|
||||
else:
|
||||
for gl in gl_entries:
|
||||
if gl.posting_date >= getdate(filters.from_date) and gl.posting_date <= getdate(filters.to_date):
|
||||
data.append(gl)
|
||||
|
||||
|
||||
# Total debit and credit between from and to date
|
||||
if total_debit or total_credit:
|
||||
data.append({
|
||||
"account": "'" + _("Totals") + "'",
|
||||
"debit": total_debit,
|
||||
"account": "'" + _("Totals") + "'",
|
||||
"debit": total_debit,
|
||||
"credit": total_credit,
|
||||
"debit_in_account_currency": total_debit_in_account_currency,
|
||||
"debit_in_account_currency": total_debit_in_account_currency,
|
||||
"credit_in_account_currency": total_credit_in_account_currency
|
||||
})
|
||||
|
||||
@@ -191,7 +197,7 @@ def get_data_with_opening_closing(filters, account_details, gl_entries):
|
||||
closing = opening + total_debit - total_credit
|
||||
closing_in_account_currency = opening_in_account_currency + \
|
||||
total_debit_in_account_currency - total_credit_in_account_currency
|
||||
|
||||
|
||||
data.append(get_balance_row(_("Closing (Opening + Totals)"),
|
||||
closing, closing_in_account_currency))
|
||||
|
||||
@@ -216,38 +222,38 @@ def initialize_gle_map(gl_entries):
|
||||
def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
opening, total_debit, total_credit = 0, 0, 0
|
||||
opening_in_account_currency, total_debit_in_account_currency, total_credit_in_account_currency = 0, 0, 0
|
||||
|
||||
|
||||
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
|
||||
for gle in gl_entries:
|
||||
amount = flt(gle.debit, 3) - flt(gle.credit, 3)
|
||||
amount_in_account_currency = flt(gle.debit_in_account_currency, 3) - flt(gle.credit_in_account_currency, 3)
|
||||
|
||||
|
||||
if (filters.get("account") or filters.get("party") or filters.get("group_by_account")) \
|
||||
and (gle.posting_date < from_date or cstr(gle.is_opening) == "Yes"):
|
||||
|
||||
|
||||
gle_map[gle.account].opening += amount
|
||||
if filters.get("show_in_account_currency"):
|
||||
gle_map[gle.account].opening_in_account_currency += amount_in_account_currency
|
||||
|
||||
|
||||
if filters.get("account") or filters.get("party"):
|
||||
opening += amount
|
||||
if filters.get("show_in_account_currency"):
|
||||
opening_in_account_currency += amount_in_account_currency
|
||||
|
||||
|
||||
elif gle.posting_date <= to_date:
|
||||
gle_map[gle.account].entries.append(gle)
|
||||
gle_map[gle.account].total_debit += flt(gle.debit, 3)
|
||||
gle_map[gle.account].total_credit += flt(gle.credit, 3)
|
||||
|
||||
|
||||
total_debit += flt(gle.debit, 3)
|
||||
total_credit += flt(gle.credit, 3)
|
||||
|
||||
|
||||
if filters.get("show_in_account_currency"):
|
||||
gle_map[gle.account].total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3)
|
||||
gle_map[gle.account].total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
|
||||
|
||||
|
||||
total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3)
|
||||
total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
|
||||
total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
|
||||
|
||||
return opening, total_debit, total_credit, opening_in_account_currency, \
|
||||
total_debit_in_account_currency, total_credit_in_account_currency, gle_map
|
||||
@@ -258,27 +264,27 @@ def get_balance_row(label, balance, balance_in_account_currency=None):
|
||||
"debit": balance if balance > 0 else 0,
|
||||
"credit": -1*balance if balance < 0 else 0
|
||||
}
|
||||
|
||||
|
||||
if balance_in_account_currency != None:
|
||||
balance_row.update({
|
||||
"debit_in_account_currency": balance_in_account_currency if balance_in_account_currency > 0 else 0,
|
||||
"credit_in_account_currency": -1*balance_in_account_currency if balance_in_account_currency < 0 else 0
|
||||
})
|
||||
|
||||
|
||||
return balance_row
|
||||
|
||||
def get_result_as_list(data, filters):
|
||||
result = []
|
||||
for d in data:
|
||||
row = [d.get("posting_date"), d.get("account"), d.get("debit"), d.get("credit")]
|
||||
|
||||
|
||||
if filters.get("show_in_account_currency"):
|
||||
row += [d.get("debit_in_account_currency"), d.get("credit_in_account_currency")]
|
||||
|
||||
row += [d.get("voucher_type"), d.get("voucher_no"), d.get("against"),
|
||||
|
||||
row += [d.get("voucher_type"), d.get("voucher_no"), d.get("against"),
|
||||
d.get("party_type"), d.get("party"), d.get("cost_center"), d.get("remarks")
|
||||
]
|
||||
|
||||
|
||||
result.append(row)
|
||||
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Trial Balance for Party"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
query_report.filters_by_name.from_date.set_input(fy.year_start_date);
|
||||
query_report.filters_by_name.to_date.set_input(fy.year_end_date);
|
||||
query_report.trigger_refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Customer", "Supplier"],
|
||||
"default": "Customer"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2015-09-22 10:28:45.762272",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"modified": "2015-09-22 10:28:45.762272",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Trial Balance for Party",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Trial Balance for Party",
|
||||
"report_type": "Script Report"
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, cint
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
validate_filters(filters)
|
||||
|
||||
show_party_name = is_party_name_visible(filters)
|
||||
|
||||
columns = get_columns(filters, show_party_name)
|
||||
data = get_data(filters, show_party_name)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(filters, show_party_name):
|
||||
party_name_field = "customer_name" if filters.get("party_type")=="Customer" else "supplier_name"
|
||||
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], order_by="name")
|
||||
|
||||
opening_balances = get_opening_balances(filters)
|
||||
balances_within_period = get_balances_within_period(filters)
|
||||
|
||||
data = []
|
||||
total_debit, total_credit = 0, 0
|
||||
for party in parties:
|
||||
row = { "party": party.name }
|
||||
if show_party_name:
|
||||
row["party_name"] = party.get(party_name_field)
|
||||
|
||||
# opening
|
||||
opening_debit, opening_credit = opening_balances.get(party.name, [0, 0])
|
||||
row.update({
|
||||
"opening_debit": opening_debit,
|
||||
"opening_credit": opening_credit
|
||||
})
|
||||
|
||||
# within period
|
||||
debit, credit = balances_within_period.get(party.name, [0, 0])
|
||||
row.update({
|
||||
"debit": debit,
|
||||
"credit": credit
|
||||
})
|
||||
|
||||
# totals
|
||||
total_debit += debit
|
||||
total_credit += credit
|
||||
|
||||
# closing
|
||||
closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit)
|
||||
row.update({
|
||||
"closing_debit": closing_debit,
|
||||
"closing_credit": closing_credit
|
||||
})
|
||||
|
||||
has_value = False
|
||||
if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit):
|
||||
has_value =True
|
||||
|
||||
if cint(filters.show_zero_values) or has_value:
|
||||
data.append(row)
|
||||
|
||||
# Add total row
|
||||
if total_debit or total_credit:
|
||||
data.append({
|
||||
"party": "'" + _("Totals") + "'",
|
||||
"debit": total_debit,
|
||||
"credit": total_credit
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
def get_opening_balances(filters):
|
||||
gle = frappe.db.sql("""
|
||||
select party, sum(ifnull(debit, 0)) as opening_debit, sum(ifnull(credit, 0)) as opening_credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
||||
group by party""", {
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"party_type": filters.party_type
|
||||
}, as_dict=True)
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit)
|
||||
opening.setdefault(d.party, [opening_debit, opening_credit])
|
||||
|
||||
return opening
|
||||
|
||||
def get_balances_within_period(filters):
|
||||
gle = frappe.db.sql("""
|
||||
select party, sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
|
||||
and ifnull(is_opening, 'No') = 'No'
|
||||
group by party""", {
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"party_type": filters.party_type
|
||||
}, as_dict=True)
|
||||
|
||||
balances_within_period = frappe._dict()
|
||||
for d in gle:
|
||||
balances_within_period.setdefault(d.party, [d.debit, d.credit])
|
||||
|
||||
return balances_within_period
|
||||
|
||||
def toggle_debit_credit(debit, credit):
|
||||
if flt(debit) > flt(credit):
|
||||
debit = flt(debit) - flt(credit)
|
||||
credit = 0.0
|
||||
else:
|
||||
credit = flt(credit) - flt(debit)
|
||||
debit = 0.0
|
||||
|
||||
return debit, credit
|
||||
|
||||
def get_columns(filters, show_party_name):
|
||||
columns = [
|
||||
{
|
||||
"fieldname": "party",
|
||||
"label": _(filters.party_type),
|
||||
"fieldtype": "Link",
|
||||
"options": filters.party_type,
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "opening_debit",
|
||||
"label": _("Opening (Dr)"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "opening_credit",
|
||||
"label": _("Opening (Cr)"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": _("Debit"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": _("Credit"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "closing_debit",
|
||||
"label": _("Closing (Dr)"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "closing_credit",
|
||||
"label": _("Closing (Cr)"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 120
|
||||
}
|
||||
]
|
||||
|
||||
if show_party_name:
|
||||
columns.insert(1, {
|
||||
"fieldname": "party_name",
|
||||
"label": _(filters.party_type) + " Name",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
})
|
||||
|
||||
return columns
|
||||
|
||||
def is_party_name_visible(filters):
|
||||
show_party_name = False
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
else:
|
||||
party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
if party_naming_by == "Naming Series":
|
||||
show_party_name = True
|
||||
|
||||
return show_party_name
|
||||
@@ -9,6 +9,9 @@ from frappe import throw, _
|
||||
from frappe.utils import formatdate
|
||||
import frappe.desk.reportview
|
||||
|
||||
# imported to enable erpnext.accounts.utils.get_account_currency
|
||||
from erpnext.accounts.doctype.account.account import get_account_currency
|
||||
|
||||
class FiscalYearError(frappe.ValidationError): pass
|
||||
class BudgetError(frappe.ValidationError): pass
|
||||
|
||||
@@ -94,8 +97,8 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, in_acco
|
||||
select name from `tabAccount` ac where ac.name = gle.account
|
||||
and ac.lft >= %s and ac.rgt <= %s
|
||||
)""" % (acc.lft, acc.rgt))
|
||||
|
||||
# If group and currency same as company,
|
||||
|
||||
# If group and currency same as company,
|
||||
# always return balance based on debit and credit in company currency
|
||||
if acc.account_currency == frappe.db.get_value("Company", acc.company, "default_currency"):
|
||||
in_account_currency = False
|
||||
@@ -195,6 +198,8 @@ def update_against_doc(d, jv_obj):
|
||||
"""
|
||||
jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||
jv_detail.set(d["dr_or_cr"], d["allocated_amt"])
|
||||
jv_detail.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit',
|
||||
d["allocated_amt"]*flt(jv_detail.exchange_rate))
|
||||
|
||||
original_reference_type = jv_detail.reference_type
|
||||
original_reference_name = jv_detail.reference_name
|
||||
@@ -207,6 +212,9 @@ def update_against_doc(d, jv_obj):
|
||||
select cost_center, balance, against_account, is_advance, account_type, exchange_rate
|
||||
from `tabJournal Entry Account` where name = %s
|
||||
""", d['voucher_detail_no'], as_dict=True)
|
||||
|
||||
amount_in_account_currency = flt(d['unadjusted_amt']) - flt(d['allocated_amt'])
|
||||
amount_in_company_currency = amount_in_account_currency * flt(jvd[0]['exchange_rate'])
|
||||
|
||||
# new entry with balance amount
|
||||
ch = jv_obj.append("accounts")
|
||||
@@ -217,8 +225,14 @@ def update_against_doc(d, jv_obj):
|
||||
ch.party = d["party"]
|
||||
ch.cost_center = cstr(jvd[0]["cost_center"])
|
||||
ch.balance = flt(jvd[0]["balance"])
|
||||
ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt']))
|
||||
ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0)
|
||||
|
||||
ch.set(d['dr_or_cr'], amount_in_account_currency)
|
||||
ch.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit', amount_in_company_currency)
|
||||
|
||||
ch.set('credit_in_account_currency' if d['dr_or_cr']== 'debit_in_account_currency'
|
||||
else 'debit_in_account_currency', 0)
|
||||
ch.set('credit' if d['dr_or_cr']== 'debit_in_account_currency' else 'debit', 0)
|
||||
|
||||
ch.against_account = cstr(jvd[0]["against_account"])
|
||||
ch.reference_type = original_reference_type
|
||||
ch.reference_name = original_reference_name
|
||||
|
||||
@@ -528,7 +528,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -1468,6 +1468,7 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@@ -1478,7 +1479,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nSubmitted\nStopped\nCancelled",
|
||||
"options": "\nDraft\nTo Receive and Bill\nTo Bill\nTo Receive\nCompleted\nStopped\nCancelled",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
@@ -1704,7 +1705,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "supplied_items",
|
||||
@@ -2032,7 +2033,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-11 12:19:55.502661",
|
||||
"modified": "2015-10-02 07:17:59.659036",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -36,13 +36,7 @@ class PurchaseOrder(BuyingController):
|
||||
def validate(self):
|
||||
super(PurchaseOrder, self).validate()
|
||||
|
||||
if not self.status:
|
||||
self.status = "Draft"
|
||||
|
||||
from erpnext.controllers.status_updater import validate_status
|
||||
validate_status(self.status, ["Draft", "Submitted", "Stopped",
|
||||
"Cancelled"])
|
||||
|
||||
self.set_status()
|
||||
pc_obj = frappe.get_doc('Purchase Common')
|
||||
pc_obj.validate_for_items(self)
|
||||
self.check_for_stopped_status(pc_obj)
|
||||
@@ -160,12 +154,10 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
def update_status(self, status):
|
||||
self.check_modified_date()
|
||||
frappe.db.set(self,'status',cstr(status))
|
||||
|
||||
self.db_set('status', status)
|
||||
self.set_status(update=True)
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
|
||||
msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status))
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
@@ -183,8 +175,6 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
purchase_controller.update_last_purchase_rate(self, is_submit = 1)
|
||||
|
||||
frappe.db.set(self,'status','Submitted')
|
||||
|
||||
def on_cancel(self):
|
||||
pc_obj = frappe.get_doc('Purchase Common')
|
||||
self.check_for_stopped_status(pc_obj)
|
||||
@@ -238,7 +228,7 @@ def stop_or_unstop_purchase_orders(names, status):
|
||||
po.update_status("Stopped")
|
||||
else:
|
||||
if po.status == "Stopped":
|
||||
po.update_status("Submitted")
|
||||
po.update_status("Draft")
|
||||
|
||||
frappe.local.message_log = []
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ from frappe import msgprint, _
|
||||
from frappe.model.naming import make_autoname
|
||||
from erpnext.utilities.address_and_contact import load_address_and_contact
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.accounts.party import validate_accounting_currency, validate_party_account
|
||||
from erpnext.accounts.party import validate_party_accounts
|
||||
|
||||
class Supplier(TransactionBase):
|
||||
def get_feed(self):
|
||||
@@ -45,9 +45,8 @@ class Supplier(TransactionBase):
|
||||
if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
|
||||
if not self.naming_series:
|
||||
msgprint(_("Series is mandatory"), raise_exception=1)
|
||||
|
||||
validate_accounting_currency(self)
|
||||
validate_party_account(self)
|
||||
|
||||
validate_party_accounts(self)
|
||||
|
||||
def get_contacts(self,nm):
|
||||
if nm:
|
||||
@@ -96,14 +95,14 @@ def get_dashboard_info(supplier):
|
||||
billing_this_year = frappe.db.sql("""
|
||||
select sum(ifnull(credit_in_account_currency, 0)) - sum(ifnull(debit_in_account_currency, 0))
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and party_type = 'Supplier'
|
||||
and party=%s and fiscal_year = %s""",
|
||||
where voucher_type='Purchase Invoice' and party_type = 'Supplier'
|
||||
and party=%s and fiscal_year = %s""",
|
||||
(supplier, frappe.db.get_default("fiscal_year")))
|
||||
|
||||
|
||||
total_unpaid = frappe.db.sql("""select sum(outstanding_amount)
|
||||
from `tabPurchase Invoice`
|
||||
where supplier=%s and docstatus = 1""", supplier)
|
||||
|
||||
|
||||
|
||||
out["billing_this_year"] = billing_this_year[0][0] if billing_this_year else 0
|
||||
out["total_unpaid"] = total_unpaid[0][0] if total_unpaid else 0
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier",
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier",
|
||||
"supplier_type": "_Test Supplier Type"
|
||||
},
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier 1",
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier 1",
|
||||
"supplier_type": "_Test Supplier Type"
|
||||
},
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier USD",
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier USD",
|
||||
"supplier_type": "_Test Supplier Type",
|
||||
"party_account_currency": "USD",
|
||||
"accounts": [{
|
||||
"company": "_Test Company",
|
||||
"account": "_Test Payable USD - _TC"
|
||||
}]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -509,7 +509,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -1554,7 +1554,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-11 12:20:10.684388",
|
||||
"modified": "2015-09-30 08:52:51.539634",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
4
erpnext/change_log/v6/v6_4_0.md
Normal file
4
erpnext/change_log/v6/v6_4_0.md
Normal file
@@ -0,0 +1,4 @@
|
||||
- Trial Balance for Customer and Supplier
|
||||
- Chart of Accounts for Guatemala
|
||||
- Address and Contact permissions based on Customer and Supplier
|
||||
- Multi-currency Accounting: Allow Accounts in different currencies for a Customer or Supplier, if they belong to a different Company
|
||||
@@ -187,6 +187,12 @@ def get_data():
|
||||
"doctype": "GL Entry",
|
||||
"is_query_report": True,
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"name": "Trial Balance for Party",
|
||||
"doctype": "GL Entry",
|
||||
"is_query_report": True,
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"name": "Gross Profit",
|
||||
|
||||
@@ -6,16 +6,15 @@ import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.utils import today, flt, cint
|
||||
from erpnext.setup.utils import get_company_currency, get_exchange_rate
|
||||
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
|
||||
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year, get_account_currency
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
from erpnext.accounts.party import get_party_account_currency, validate_party_gle_currency
|
||||
from erpnext.exceptions import CustomerFrozen, InvalidCurrency
|
||||
|
||||
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
|
||||
|
||||
class CustomerFrozen(frappe.ValidationError): pass
|
||||
class InvalidCurrency(frappe.ValidationError): pass
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(AccountsController, self).__init__(arg1, arg2)
|
||||
@@ -169,7 +168,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if item.price_list_rate:
|
||||
item.rate = flt(item.price_list_rate *
|
||||
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
|
||||
|
||||
def set_taxes(self):
|
||||
if not self.meta.get_field("taxes"):
|
||||
@@ -220,7 +219,7 @@ class AccountsController(TransactionBase):
|
||||
gl_dict.update(args)
|
||||
|
||||
if not account_currency:
|
||||
account_currency = frappe.db.get_value("Account", gl_dict.account, "account_currency")
|
||||
account_currency = get_account_currency(gl_dict.account)
|
||||
|
||||
if self.doctype != "Journal Entry":
|
||||
self.validate_account_currency(gl_dict.account, account_currency)
|
||||
@@ -427,10 +426,12 @@ class AccountsController(TransactionBase):
|
||||
if self.get("currency"):
|
||||
party_type, party = self.get_party()
|
||||
if party_type and party:
|
||||
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency") \
|
||||
or self.company_currency
|
||||
party_account_currency = get_party_account_currency(party_type, party, self.company)
|
||||
|
||||
if (party_account_currency
|
||||
and party_account_currency != self.company_currency
|
||||
and self.currency != party_account_currency):
|
||||
|
||||
if party_account_currency != self.company_currency and self.currency != party_account_currency:
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
.format(party_type, party, party_account_currency), InvalidCurrency)
|
||||
|
||||
|
||||
@@ -30,7 +30,19 @@ status_map = {
|
||||
],
|
||||
"Sales Order": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.docstatus==1"],
|
||||
["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Stopped", "eval:self.status=='Stopped'"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
],
|
||||
"Purchase Order": [
|
||||
["Draft", None],
|
||||
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Bill", "eval:self.per_received == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Stopped", "eval:self.status=='Stopped'"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
],
|
||||
@@ -207,10 +219,10 @@ class StatusUpdater(Document):
|
||||
# update percent complete in the parent table
|
||||
if args.get('target_parent_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
set %(target_parent_field)s = round((select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
from `tab%(target_dt)s` where parent="%(name)s") %(set_modified)s
|
||||
from `tab%(target_dt)s` where parent="%(name)s"), 2) %(set_modified)s
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
# update field
|
||||
@@ -222,7 +234,9 @@ class StatusUpdater(Document):
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
if args.get("set_modified"):
|
||||
frappe.get_doc(args["target_parent_dt"], name).notify_update()
|
||||
target = frappe.get_doc(args["target_parent_dt"], name)
|
||||
target.set_status(update=True)
|
||||
target.notify_update()
|
||||
|
||||
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
||||
ref_fieldname = ref_dt.lower().replace(" ", "_")
|
||||
|
||||
@@ -35,6 +35,8 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
||||
key, parties = "customer", customers
|
||||
elif suppliers:
|
||||
key, parties = "supplier", suppliers
|
||||
else:
|
||||
key, parties = "customer", []
|
||||
|
||||
filters.append((doctype, key, "in", parties))
|
||||
|
||||
|
||||
@@ -58,12 +58,12 @@ class Lead(SellingController):
|
||||
def check_email_id_is_unique(self):
|
||||
if self.email_id:
|
||||
# validate email is unique
|
||||
email_list = frappe.db.sql("""select name from tabLead where email_id=%s""",
|
||||
self.email_id)
|
||||
email_list = [e[0] for e in email_list if e[0]!=self.name]
|
||||
if len(email_list) > 1:
|
||||
frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(email_list)),
|
||||
frappe.DuplicateEntryError)
|
||||
duplicate_leads = frappe.db.sql_list("""select name from tabLead
|
||||
where email_id=%s and name!=%s""", (self.email_id, self.name))
|
||||
|
||||
if duplicate_leads:
|
||||
frappe.throw(_("Email id must be unique, already exists for {0}")
|
||||
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""",
|
||||
|
||||
@@ -62,7 +62,8 @@ class Newsletter(Document):
|
||||
subject = self.subject, message = self.message,
|
||||
reference_doctype = self.doctype, reference_name = self.name,
|
||||
unsubscribe_method = "/api/method/erpnext.crm.doctype.newsletter.newsletter.unsubscribe",
|
||||
unsubscribe_params = {"name": self.newsletter_list})
|
||||
unsubscribe_params = {"name": self.newsletter_list},
|
||||
bulk_priority = 1)
|
||||
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
|
||||
@@ -7,8 +7,8 @@ from frappe.utils import cstr, cint
|
||||
from frappe import msgprint, _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
|
||||
subject_field = "title"
|
||||
sender_field = "contact_email"
|
||||
@@ -180,9 +180,10 @@ def get_item_details(item_code):
|
||||
def make_quotation(source_name, target_doc=None):
|
||||
def set_missing_values(source, target):
|
||||
quotation = frappe.get_doc(target)
|
||||
|
||||
|
||||
company_currency = frappe.db.get_value("Company", quotation.company, "default_currency")
|
||||
party_account_currency = frappe.db.get_value("Customer", quotation.customer, "party_account_currency")
|
||||
party_account_currency = get_party_account_currency("Customer", quotation.customer, quotation.company)
|
||||
|
||||
if company_currency == party_account_currency:
|
||||
exchange_rate = 1
|
||||
else:
|
||||
@@ -190,7 +191,7 @@ def make_quotation(source_name, target_doc=None):
|
||||
|
||||
quotation.currency = party_account_currency or company_currency
|
||||
quotation.conversion_rate = exchange_rate
|
||||
|
||||
|
||||
quotation.run_method("set_missing_values")
|
||||
quotation.run_method("calculate_taxes_and_totals")
|
||||
|
||||
|
||||
7
erpnext/exceptions.py
Normal file
7
erpnext/exceptions.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
# accounts
|
||||
class CustomerFrozen(frappe.ValidationError): pass
|
||||
class InvalidAccountCurrency(frappe.ValidationError): pass
|
||||
class InvalidCurrency(frappe.ValidationError): pass
|
||||
@@ -29,7 +29,7 @@ blogs.
|
||||
"""
|
||||
app_icon = "icon-th"
|
||||
app_color = "#e74c3c"
|
||||
app_version = "6.3.2"
|
||||
app_version = "6.4.6"
|
||||
github_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
@@ -53,7 +53,7 @@ my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
|
||||
|
||||
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
|
||||
|
||||
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order"]
|
||||
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order", "Holiday List"]
|
||||
|
||||
website_generators = ["Item Group", "Item", "Sales Partner"]
|
||||
|
||||
@@ -93,6 +93,16 @@ has_website_permission = {
|
||||
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission"
|
||||
}
|
||||
|
||||
permission_query_conditions = {
|
||||
"Contact": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_contact",
|
||||
"Address": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_address"
|
||||
}
|
||||
|
||||
has_permission = {
|
||||
"Contact": "erpnext.utilities.address_and_contact.has_permission",
|
||||
"Address": "erpnext.utilities.address_and_contact.has_permission"
|
||||
}
|
||||
|
||||
dump_report_map = "erpnext.startup.report_data_map.data_map"
|
||||
|
||||
before_tests = "erpnext.setup.utils.before_tests"
|
||||
@@ -128,12 +138,6 @@ scheduler_events = {
|
||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
|
||||
"erpnext.hr.doctype.employee.employee.send_birthday_reminders"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_daily"
|
||||
],
|
||||
"weekly_long": [
|
||||
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_weekly"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ class HolidayList(Document):
|
||||
|
||||
def get_weekly_off_dates(self):
|
||||
self.validate_values()
|
||||
yr_start_date, yr_end_date = self.get_fy_start_end_dates()
|
||||
self.validate_days()
|
||||
yr_start_date, yr_end_date = get_fy_start_end_dates(self.fiscal_year)
|
||||
date_list = self.get_weekly_off_date_list(yr_start_date, yr_end_date)
|
||||
last_idx = max([cint(d.idx) for d in self.get("holidays")] or [0,])
|
||||
for i, d in enumerate(date_list):
|
||||
@@ -30,10 +31,11 @@ class HolidayList(Document):
|
||||
throw(_("Please select Fiscal Year"))
|
||||
if not self.weekly_off:
|
||||
throw(_("Please select weekly off day"))
|
||||
|
||||
def get_fy_start_end_dates(self):
|
||||
return frappe.db.sql("""select year_start_date, year_end_date
|
||||
from `tabFiscal Year` where name=%s""", (self.fiscal_year,))[0]
|
||||
|
||||
def validate_days(self):
|
||||
for day in self.get("holidays"):
|
||||
if (self.weekly_off).upper() == (day.description).upper():
|
||||
frappe.throw("Records alredy exist for mentioned weekly off")
|
||||
|
||||
def get_weekly_off_date_list(self, year_start_date, year_end_date):
|
||||
from frappe.utils import getdate
|
||||
@@ -59,3 +61,39 @@ class HolidayList(Document):
|
||||
def update_default_holiday_list(self):
|
||||
frappe.db.sql("""update `tabHoliday List` set is_default = 0
|
||||
where ifnull(is_default, 0) = 1 and fiscal_year = %s""", (self.fiscal_year,))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
import json
|
||||
"""Returns events for Gantt / Calendar view rendering.
|
||||
|
||||
:param start: Start date-time.
|
||||
:param end: End date-time.
|
||||
:param filters: Filters (JSON).
|
||||
"""
|
||||
from frappe.desk.calendar import get_event_conditions
|
||||
conditions = get_event_conditions("Holiday List", filters)
|
||||
|
||||
fiscal_year = None
|
||||
if filters:
|
||||
fiscal_year = json.loads(filters).get("fiscal_year")
|
||||
|
||||
if not fiscal_year:
|
||||
fiscal_year = frappe.db.get_value("Global Defaults", None, "current_fiscal_year")
|
||||
|
||||
yr_start_date, yr_end_date = get_fy_start_end_dates(fiscal_year)
|
||||
|
||||
data = frappe.db.sql("""select hl.name, hld.holiday_date, hld.description
|
||||
from `tabHoliday List` hl, tabHoliday hld
|
||||
where hld.parent = hl.name
|
||||
and (ifnull(hld.holiday_date, "0000-00-00") != "0000-00-00"
|
||||
and hld.holiday_date between %(start)s and %(end)s)
|
||||
{conditions}""".format(conditions=conditions), {
|
||||
"start": yr_start_date,
|
||||
"end": yr_end_date
|
||||
}, as_dict=True, update={"allDay": 1})
|
||||
|
||||
return data
|
||||
|
||||
def get_fy_start_end_dates(fiscal_year):
|
||||
return frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"])
|
||||
21
erpnext/hr/doctype/holiday_list/holiday_list_calendar.js
Normal file
21
erpnext/hr/doctype/holiday_list/holiday_list_calendar.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.views.calendar["Holiday List"] = {
|
||||
field_map: {
|
||||
"start": "holiday_date",
|
||||
"end": "holiday_date",
|
||||
"id": "name",
|
||||
"title": "description",
|
||||
"allDay": "allDay"
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "fiscal_year",
|
||||
"options": "Fiscal Year",
|
||||
"label": __("Fiscal Year")
|
||||
}
|
||||
],
|
||||
get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events"
|
||||
}
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe import msgprint, _
|
||||
from calendar import monthrange
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
@@ -73,23 +74,17 @@ def get_conditions(filters):
|
||||
msgprint(_("Please select month and year"), raise_exception=1)
|
||||
|
||||
filters["month"] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
|
||||
"Dec"].index(filters["month"]) + 1
|
||||
"Dec"].index(filters.month) + 1
|
||||
|
||||
from frappe.model.document import Document
|
||||
fiscal_years = frappe.get_doc("Fiscal Year",filters["fiscal_year"])
|
||||
import datetime
|
||||
year_start = fiscal_years.year_start_date.strftime("%Y")
|
||||
year_end = fiscal_years.year_end_date.strftime("%Y")
|
||||
dt_test = datetime.datetime.strptime(year_end + "-" + str(100+int(filters["month"]))[2:3] + "-01", "%Y-%m-%d")
|
||||
date_test = datetime.date(dt_test.year, dt_test.month, dt_test.day)
|
||||
if date_test > fiscal_years.year_end_date:
|
||||
year_target = year_start
|
||||
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year", filters.fiscal_year,
|
||||
["year_start_date", "year_end_date"])
|
||||
|
||||
if filters.month >= year_start_date.strftime("%m"):
|
||||
year = year_start_date.strftime("%Y")
|
||||
else:
|
||||
year_target = year_end
|
||||
|
||||
from calendar import monthrange
|
||||
filters["total_days_in_month"] = monthrange(cint(year_target),
|
||||
filters["month"])[1]
|
||||
year = year_end_date.strftime("%Y")
|
||||
|
||||
filters["total_days_in_month"] = monthrange(cint(year), filters.month)[1]
|
||||
|
||||
conditions = " and month(att_date) = %(month)s and fiscal_year = %(fiscal_year)s"
|
||||
|
||||
|
||||
@@ -391,8 +391,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1):
|
||||
|
||||
if fetch_exploded:
|
||||
query = query.format(table="BOM Explosion Item",
|
||||
conditions="""and item.is_pro_applicable = 0
|
||||
and item.is_sub_contracted_item = 0 """)
|
||||
conditions="""and item.is_sub_contracted_item = 0""")
|
||||
items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
|
||||
else:
|
||||
query = query.format(table="BOM Item", conditions="")
|
||||
|
||||
@@ -211,3 +211,8 @@ erpnext.patches.v6_2.remove_newsletter_duplicates
|
||||
erpnext.patches.v6_2.fix_missing_default_taxes_and_lead
|
||||
erpnext.patches.v5_8.tax_rule
|
||||
erpnext.patches.v6_3.convert_applicable_territory
|
||||
erpnext.patches.v6_4.round_status_updater_percentages
|
||||
erpnext.patches.v6_4.repost_gle_for_journal_entries_where_reference_name_missing
|
||||
erpnext.patches.v6_4.fix_journal_entries_due_to_reconciliation
|
||||
erpnext.patches.v6_4.fix_status_in_sales_and_purchase_order
|
||||
erpnext.patches.v6_4.fix_modified_in_sales_order_and_purchase_order
|
||||
@@ -63,56 +63,3 @@ def execute():
|
||||
where
|
||||
company=%s
|
||||
""", (company.default_currency, company.name))
|
||||
|
||||
# Set party account if default currency of party other than company's default currency
|
||||
for dt in ("Customer", "Supplier"):
|
||||
parties = frappe.get_all(dt, filters={"docstatus": 0})
|
||||
for p in parties:
|
||||
party = frappe.get_doc(dt, p.name)
|
||||
party_accounts = []
|
||||
|
||||
for company in company_list:
|
||||
# Get party GL Entries
|
||||
party_gle = frappe.db.get_value("GL Entry", {"party_type": dt, "party": p.name,
|
||||
"company": company.name}, ["account", "account_currency", "name"], as_dict=True)
|
||||
|
||||
# set party account currency
|
||||
if party_gle:
|
||||
party.party_account_currency = party_gle.account_currency
|
||||
elif not party.party_account_currency:
|
||||
party.party_account_currency = company.default_currency
|
||||
|
||||
# Add default receivable /payable account if not exists
|
||||
# and currency is other than company currency
|
||||
if party.party_account_currency and party.party_account_currency != company.default_currency:
|
||||
party_account_exists_for_company = False
|
||||
for d in party.get("accounts"):
|
||||
if d.company == company.name:
|
||||
account_currency = frappe.db.get_value("Account", d.account, "account_currency")
|
||||
if account_currency == party.party_account_currency:
|
||||
party_accounts.append({
|
||||
"company": d.company,
|
||||
"account": d.account
|
||||
})
|
||||
party_account_exists_for_company = True
|
||||
break
|
||||
|
||||
if not party_account_exists_for_company:
|
||||
party_account = None
|
||||
if party_gle:
|
||||
party_account = party_gle.account
|
||||
else:
|
||||
default_receivable_account_currency = frappe.db.get_value("Account",
|
||||
company.default_receivable_account, "account_currency")
|
||||
if default_receivable_account_currency != company.default_currency:
|
||||
party_account = company.default_receivable_account
|
||||
|
||||
if party_account:
|
||||
party_accounts.append({
|
||||
"company": company.name,
|
||||
"account": party_account
|
||||
})
|
||||
|
||||
party.set("accounts", party_accounts)
|
||||
party.flags.ignore_mandatory = True
|
||||
party.save()
|
||||
|
||||
1
erpnext/patches/v6_4/__init__.py
Normal file
1
erpnext/patches/v6_4/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
je_rows = frappe.db.sql("""
|
||||
select name, parent, reference_type, reference_name, debit, credit
|
||||
from `tabJournal Entry Account`
|
||||
where docstatus=1 and date(modified) >= '2015-09-17'
|
||||
and ((ifnull(debit_in_account_currency, 0)*exchange_rate != ifnull(debit, 0))
|
||||
or (ifnull(credit_in_account_currency, 0)*exchange_rate != ifnull(credit, 0)))
|
||||
order by parent
|
||||
""", as_dict=True)
|
||||
|
||||
journal_entries = []
|
||||
|
||||
for d in je_rows:
|
||||
if d.parent not in journal_entries:
|
||||
journal_entries.append(d.parent)
|
||||
|
||||
is_advance_entry=None
|
||||
if d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name:
|
||||
is_advance_entry = frappe.db.sql("""select name from `tab{0}`
|
||||
where journal_entry=%s and jv_detail_no=%s
|
||||
and ifnull(allocated_amount, 0) > 0 and docstatus=1"""
|
||||
.format(d.reference_type + " Advance"), (d.parent, d.name))
|
||||
|
||||
if is_advance_entry or not (d.debit or d.credit):
|
||||
frappe.db.sql("""
|
||||
update `tabJournal Entry Account`
|
||||
set debit=debit_in_account_currency*exchange_rate,
|
||||
credit=credit_in_account_currency*exchange_rate
|
||||
where name=%s""", d.name)
|
||||
else:
|
||||
frappe.db.sql("""
|
||||
update `tabJournal Entry Account`
|
||||
set debit_in_account_currency=debit/exchange_rate,
|
||||
credit_in_account_currency=credit/exchange_rate
|
||||
where name=%s""", d.name)
|
||||
|
||||
for d in journal_entries:
|
||||
print d
|
||||
# delete existing gle
|
||||
frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d)
|
||||
|
||||
# repost gl entries
|
||||
je = frappe.get_doc("Journal Entry", d)
|
||||
je.make_gl_entries()
|
||||
@@ -0,0 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for doctype in ("Sales Order", "Purchase Order"):
|
||||
data = frappe.db.sql("""select parent, modified_by, modified
|
||||
from `tab{doctype} Item` where docstatus=1 group by parent""".format(doctype=doctype), as_dict=True)
|
||||
for item in data:
|
||||
frappe.db.sql("""update `tab{doctype}` set modified_by=%(modified_by)s, modified=%(modified)s
|
||||
where name=%(parent)s""".format(doctype=doctype), item)
|
||||
@@ -0,0 +1,7 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for doctype in ("Sales Order", "Purchase Order"):
|
||||
for doc in frappe.get_all(doctype, filters={"docstatus": 1}):
|
||||
doc = frappe.get_doc(doctype, doc.name)
|
||||
doc.set_status(update=True)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
je_list = frappe.db.sql_list("""select distinct parent from `tabJournal Entry Account` je
|
||||
where docstatus=1 and ifnull(reference_name, '') !='' and creation > '2015-03-01'
|
||||
and not exists(select name from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no=je.parent
|
||||
and against_voucher_type=je.reference_type
|
||||
and against_voucher=je.reference_name)""")
|
||||
|
||||
for d in je_list:
|
||||
print d
|
||||
|
||||
# delete existing gle
|
||||
frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d)
|
||||
|
||||
# repost gl entries
|
||||
je = frappe.get_doc("Journal Entry", d)
|
||||
je.make_gl_entries()
|
||||
14
erpnext/patches/v6_4/round_status_updater_percentages.py
Normal file
14
erpnext/patches/v6_4/round_status_updater_percentages.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for doctype, fieldname in (
|
||||
("Sales Order", "per_billed"),
|
||||
("Sales Order", "per_delivered"),
|
||||
("Delivery Note", "per_installed"),
|
||||
("Purchase Order", "per_billed"),
|
||||
("Purchase Order", "per_received"),
|
||||
("Material Request", "per_ordered"),
|
||||
):
|
||||
frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=round(`{fieldname}`, 2)""".format(
|
||||
doctype=doctype, fieldname=fieldname))
|
||||
@@ -1,6 +1,10 @@
|
||||
frappe.listview_settings['Task'] = {
|
||||
add_fields: ["project", "status", "priority", "exp_end_date"],
|
||||
onload: function(listview) {
|
||||
frappe.route_options = {
|
||||
"status": "Open"
|
||||
};
|
||||
|
||||
var method = "erpnext.projects.doctype.task.task.set_multiple_status";
|
||||
|
||||
listview.page.add_menu_item(__("Set as Open"), function() {
|
||||
|
||||
@@ -10,6 +10,7 @@ frappe.views.calendar["Time Log"] = {
|
||||
"allDay": "allDay"
|
||||
},
|
||||
gantt: true,
|
||||
gantt_scale: "hours",
|
||||
filters: [
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -34,7 +34,7 @@ class TimeLogBatch(Document):
|
||||
def validate_time_log_is_submitted(self, tl):
|
||||
if tl.status == "Batched for Billing":
|
||||
frappe.throw(_("Time Log {0} already billed").format(tl.name))
|
||||
elif tl.status != "Submitted":
|
||||
elif tl.docstatus != 1:
|
||||
frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name))
|
||||
|
||||
def set_status(self):
|
||||
|
||||
@@ -17,7 +17,7 @@ $(function() {
|
||||
|
||||
$.extend(shopping_cart, {
|
||||
update_cart: function(opts) {
|
||||
if(!full_name) {
|
||||
if(!full_name || full_name==="Guest") {
|
||||
if(localStorage) {
|
||||
localStorage.setItem("last_visited", window.location.pathname);
|
||||
localStorage.setItem("pending_add_to_cart", opts.item_code);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// start
|
||||
$(document).on('startup', function() {
|
||||
console.log(__('Starting up...'));
|
||||
});
|
||||
@@ -17,7 +17,6 @@ frappe.ui.form.on("Customer", "refresh", function(frm) {
|
||||
}
|
||||
|
||||
var grid = cur_frm.get_field("sales_team").grid;
|
||||
grid.set_column_disp("allocated_percentage", false);
|
||||
grid.set_column_disp("allocated_amount", false);
|
||||
grid.set_column_disp("incentives", false);
|
||||
|
||||
@@ -101,11 +100,11 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d
|
||||
'company': d.company,
|
||||
"is_group": 0
|
||||
};
|
||||
|
||||
|
||||
if(doc.party_account_currency) {
|
||||
$.extend(filters, {"account_currency": doc.party_account_currency});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,10 @@ from frappe.model.naming import make_autoname
|
||||
from frappe import _, msgprint, throw
|
||||
import frappe.defaults
|
||||
from frappe.utils import flt
|
||||
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.utilities.address_and_contact import load_address_and_contact
|
||||
from erpnext.accounts.party import validate_accounting_currency, validate_party_account
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
from erpnext.accounts.party import validate_party_accounts
|
||||
|
||||
class Customer(TransactionBase):
|
||||
def get_feed(self):
|
||||
@@ -33,8 +32,7 @@ class Customer(TransactionBase):
|
||||
|
||||
def validate(self):
|
||||
self.flags.is_new_doc = self.is_new()
|
||||
validate_accounting_currency(self)
|
||||
validate_party_account(self)
|
||||
validate_party_accounts(self)
|
||||
|
||||
def update_lead_status(self):
|
||||
if self.lead_name:
|
||||
@@ -197,27 +195,26 @@ def get_customer_outstanding(customer, company):
|
||||
outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
|
||||
|
||||
# Outstanding based on Delivery Note
|
||||
outstanding_based_on_dn = frappe.db.sql("""
|
||||
select
|
||||
sum(
|
||||
(
|
||||
(ifnull(dn_item.amount, 0) - ifnull((select sum(ifnull(amount, 0))
|
||||
from `tabSales Invoice Item`
|
||||
where ifnull(dn_detail, '') = dn_item.name and docstatus = 1), 0)
|
||||
)/dn.base_net_total
|
||||
)*dn.base_grand_total
|
||||
)
|
||||
unmarked_delivery_note_items = frappe.db.sql("""select
|
||||
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
|
||||
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
|
||||
where
|
||||
dn.name = dn_item.parent and dn.customer=%s and dn.company=%s
|
||||
dn.name = dn_item.parent
|
||||
and dn.customer=%s and dn.company=%s
|
||||
and dn.docstatus = 1 and dn.status != 'Stopped'
|
||||
and ifnull(dn_item.against_sales_order, '') = ''
|
||||
and ifnull(dn_item.against_sales_invoice, '') = ''
|
||||
and ifnull(dn_item.amount, 0) > ifnull((select sum(ifnull(amount, 0))
|
||||
from `tabSales Invoice Item`
|
||||
where ifnull(dn_detail, '') = dn_item.name and docstatus = 1), 0)""", (customer, company))
|
||||
and ifnull(dn_item.against_sales_invoice, '') = ''""", (customer, company), as_dict=True)
|
||||
|
||||
outstanding_based_on_dn = flt(outstanding_based_on_dn[0][0]) if outstanding_based_on_dn else 0.0
|
||||
outstanding_based_on_dn = 0.0
|
||||
|
||||
for dn_item in unmarked_delivery_note_items:
|
||||
si_amount = frappe.db.sql("""select sum(ifnull(amount, 0))
|
||||
from `tabSales Invoice Item`
|
||||
where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
|
||||
|
||||
if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
|
||||
outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
|
||||
/ dn_item.base_net_total) * dn_item.base_grand_total
|
||||
|
||||
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.controllers.accounts_controller import CustomerFrozen
|
||||
from erpnext.accounts.party import InvalidCurrency
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.exceptions import CustomerFrozen
|
||||
|
||||
test_ignore = ["Price List"]
|
||||
|
||||
@@ -37,9 +35,9 @@ class TestCustomer(unittest.TestCase):
|
||||
|
||||
make_test_records("Address")
|
||||
make_test_records("Contact")
|
||||
frappe.db.set_value("Contact", "_Test Contact For _Test Customer-_Test Customer",
|
||||
frappe.db.set_value("Contact", "_Test Contact For _Test Customer-_Test Customer",
|
||||
"is_primary_contact", 1)
|
||||
|
||||
|
||||
details = get_party_details("_Test Customer")
|
||||
|
||||
for key, value in to_check.iteritems():
|
||||
@@ -68,25 +66,15 @@ class TestCustomer(unittest.TestCase):
|
||||
{"comment_doctype": "Customer", "comment_docname": "_Test Customer 1 Renamed"}), comment.name)
|
||||
|
||||
frappe.rename_doc("Customer", "_Test Customer 1 Renamed", "_Test Customer 1")
|
||||
|
||||
|
||||
def test_freezed_customer(self):
|
||||
frappe.db.set_value("Customer", "_Test Customer", "is_frozen", 1)
|
||||
|
||||
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
so = make_sales_order(do_not_save= True)
|
||||
self.assertRaises(CustomerFrozen, so.save)
|
||||
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "is_frozen", 0)
|
||||
|
||||
|
||||
so.save()
|
||||
|
||||
def test_multi_currency(self):
|
||||
customer = frappe.get_doc("Customer", "_Test Customer USD")
|
||||
|
||||
create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
|
||||
customer.party_account_currency = "EUR"
|
||||
self.assertRaises(InvalidCurrency, customer.save)
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory",
|
||||
"party_account_currency": "USD",
|
||||
"accounts": [{
|
||||
"company": "_Test Company",
|
||||
"account": "_Test Receivable USD - _TC"
|
||||
|
||||
@@ -613,7 +613,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -1843,7 +1843,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2015-09-11 12:20:18.521489",
|
||||
"modified": "2015-09-30 08:52:54.426175",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,27 @@ form_grid_templates = {
|
||||
class WarehouseRequired(frappe.ValidationError): pass
|
||||
|
||||
class SalesOrder(SellingController):
|
||||
def validate(self):
|
||||
super(SalesOrder, self).validate()
|
||||
|
||||
self.validate_order_type()
|
||||
self.validate_delivery_date()
|
||||
self.validate_mandatory()
|
||||
self.validate_proj_cust()
|
||||
self.validate_po()
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.validate_for_items()
|
||||
self.validate_warehouse()
|
||||
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
make_packing_list(self,'items')
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
self.set_status()
|
||||
|
||||
if not self.billing_status: self.billing_status = 'Not Billed'
|
||||
if not self.delivery_status: self.delivery_status = 'Not Delivered'
|
||||
|
||||
def validate_mandatory(self):
|
||||
# validate transaction date v/s delivery date
|
||||
if self.delivery_date:
|
||||
@@ -93,33 +114,6 @@ class SalesOrder(SellingController):
|
||||
if not res:
|
||||
frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project_name))
|
||||
|
||||
def validate(self):
|
||||
super(SalesOrder, self).validate()
|
||||
|
||||
self.validate_order_type()
|
||||
self.validate_delivery_date()
|
||||
self.validate_mandatory()
|
||||
self.validate_proj_cust()
|
||||
self.validate_po()
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.validate_for_items()
|
||||
self.validate_warehouse()
|
||||
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
make_packing_list(self,'items')
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
|
||||
if not self.status:
|
||||
self.status = "Draft"
|
||||
|
||||
from erpnext.controllers.status_updater import validate_status
|
||||
validate_status(self.status, ["Draft", "Submitted", "Stopped",
|
||||
"Cancelled"])
|
||||
|
||||
if not self.billing_status: self.billing_status = 'Not Billed'
|
||||
if not self.delivery_status: self.delivery_status = 'Not Delivered'
|
||||
|
||||
def validate_warehouse(self):
|
||||
from erpnext.stock.utils import validate_warehouse_company
|
||||
|
||||
@@ -162,7 +156,6 @@ class SalesOrder(SellingController):
|
||||
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.base_grand_total, self)
|
||||
|
||||
self.update_prevdoc_status('submit')
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
def on_cancel(self):
|
||||
# Cannot cancel stopped SO
|
||||
@@ -175,7 +168,7 @@ class SalesOrder(SellingController):
|
||||
self.update_prevdoc_status('cancel')
|
||||
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
|
||||
def check_credit_limit(self):
|
||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||
check_credit_limit(self.customer, self.company)
|
||||
@@ -223,17 +216,16 @@ class SalesOrder(SellingController):
|
||||
|
||||
def stop_sales_order(self):
|
||||
self.check_modified_date()
|
||||
frappe.db.set(self, 'status', 'Stopped')
|
||||
self.db_set('status', 'Stopped')
|
||||
self.update_reserved_qty()
|
||||
frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name))
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
def unstop_sales_order(self):
|
||||
self.check_modified_date()
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
self.db_set('status', 'Draft')
|
||||
self.set_status(update=True)
|
||||
self.update_reserved_qty()
|
||||
frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name))
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
def update_reserved_qty(self, so_item_rows=None):
|
||||
|
||||
@@ -7,6 +7,8 @@ import frappe.permissions
|
||||
import unittest
|
||||
from erpnext.selling.doctype.sales_order.sales_order \
|
||||
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry \
|
||||
import make_journal_entry
|
||||
|
||||
from frappe.tests.test_permissions import set_user_permission_doctypes
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-06-21 16:46:45",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2015-03-30 05:45:40.146567",
|
||||
"modified": "2015-10-06 12:43:48.259027",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Pending SO Items For Purchase Request",
|
||||
"owner": "Administrator",
|
||||
"query": "select \n so_item.item_code as \"Item Code:Link/Item:120\",\n so_item.item_name as \"Item Name::120\",\n so_item.description as \"Description::120\",\n so.`name` as \"S.O. No.:Link/Sales Order:120\",\n so.`transaction_date` as \"Date:Date:120\",\n mr.name as \"Material Request:Link/Material Request:120\",\n so.customer as \"Customer:Link/Customer:120\",\n so.territory as \"Terretory:Link/Territory:120\",\n sum(so_item.qty) as \"SO Qty:Float:100 \",\n sum(mr_item.qty) as \"Requested Qty:Float:100\",\n so.company as \"Company:Link/Company:\"\nfrom\n `tabSales Order` so, `tabSales Order Item` so_item, \n `tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere\n so_item.`parent` = so.`name` and mr_item.sales_order_no = so.name\n and mr_item.parent = mr.name \n and so.docstatus = 1 and so.status != \"Stopped\" \n and mr.docstatus = 1 and mr.status != \"Stopped\"\ngroup by so.name, so_item.item_code\norder by so.name desc, so_item.item_code asc",
|
||||
"query": "select so_item.item_code as \"Item Code:Link/Item:120\",\n so_item.item_name as \"Item Name::120\",\n so_item.description as \"Description::120\",\n so.`name` as \"S.O. No.:Link/Sales Order:120\",\n so.`transaction_date` as \"Date:Date:120\",\n mr.name as \"Material Request:Link/Material Request:120\",\n so.customer as \"Customer:Link/Customer:120\",\n so.territory as \"Terretory:Link/Territory:120\",\n sum(so_item.qty) as \"SO Qty:Float:100 \",\n sum(mr_item.qty) as \"Requested Qty:Float:100\",\n sum(so_item.qty) - sum(mr_item.qty) as \"Pending Qty:Float:100 \", \n so.company as \"Company:Link/Company:\"\nfrom\n `tabSales Order` so, `tabSales Order Item` so_item, \n `tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere \n so_item.`parent` = so.`name` \n and mr_item.parent = mr.name\n and mr_item.sales_order_no = so.name\n and mr_item.item_code = so_item.item_code\n and so.docstatus = 1 and so.status != \"Stopped\" \n and mr.docstatus = 1 and mr.status != \"Stopped\"\ngroup by so.name, so_item.item_code\nhaving sum(so_item.qty) > sum(mr_item.qty)\norder by so.name desc, so_item.item_code asc",
|
||||
"ref_doctype": "Sales Order",
|
||||
"report_name": "Pending SO Items For Purchase Request",
|
||||
"report_type": "Query Report"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Settings to manage automated backups to third party tools like Dropbox and Google Drive.
|
||||
@@ -1,155 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# SETUP:
|
||||
# install pip install --upgrade dropbox
|
||||
#
|
||||
# Create new Dropbox App
|
||||
#
|
||||
# in conf.py, set oauth2 settings
|
||||
# dropbox_access_key
|
||||
# dropbox_access_secret
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import frappe
|
||||
from frappe.utils import get_request_site_address, cstr
|
||||
from frappe import _
|
||||
|
||||
ignore_list = [".DS_Store"]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dropbox_authorize_url():
|
||||
sess = get_dropbox_session()
|
||||
request_token = sess.obtain_request_token()
|
||||
return_address = get_request_site_address(True) \
|
||||
+ "?cmd=erpnext.setup.doctype.backup_manager.backup_dropbox.dropbox_callback"
|
||||
|
||||
url = sess.build_authorize_url(request_token, return_address)
|
||||
|
||||
return {
|
||||
"url": url,
|
||||
"key": request_token.key,
|
||||
"secret": request_token.secret,
|
||||
}
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def dropbox_callback(oauth_token=None, not_approved=False):
|
||||
from dropbox import client
|
||||
if not not_approved:
|
||||
if frappe.db.get_value("Backup Manager", None, "dropbox_access_key")==oauth_token:
|
||||
allowed = 1
|
||||
message = "Dropbox access allowed."
|
||||
|
||||
sess = get_dropbox_session()
|
||||
sess.set_request_token(frappe.db.get_value("Backup Manager", None, "dropbox_access_key"),
|
||||
frappe.db.get_value("Backup Manager", None, "dropbox_access_secret"))
|
||||
access_token = sess.obtain_access_token()
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "dropbox_access_key", access_token.key)
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "dropbox_access_secret", access_token.secret)
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "dropbox_access_allowed", allowed)
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "send_backups_to_dropbox", 1)
|
||||
dropbox_client = client.DropboxClient(sess)
|
||||
try:
|
||||
dropbox_client.file_create_folder("files")
|
||||
except:
|
||||
pass
|
||||
|
||||
else:
|
||||
allowed = 0
|
||||
message = "Illegal Access Token Please try again."
|
||||
else:
|
||||
allowed = 0
|
||||
message = "Dropbox Access not approved."
|
||||
|
||||
frappe.local.message_title = "Dropbox Approval"
|
||||
frappe.local.message = "<h3>%s</h3><p>Please close this window.</p>" % message
|
||||
|
||||
if allowed:
|
||||
frappe.local.message_success = True
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.response['type'] = 'page'
|
||||
frappe.response['page_name'] = 'message.html'
|
||||
|
||||
def backup_to_dropbox():
|
||||
from dropbox import client, session
|
||||
from frappe.utils.backups import new_backup
|
||||
from frappe.utils import get_files_path, get_backups_path
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
|
||||
sess = session.DropboxSession(frappe.conf.dropbox_access_key, frappe.conf.dropbox_secret_key, "app_folder")
|
||||
|
||||
sess.set_token(frappe.db.get_value("Backup Manager", None, "dropbox_access_key"),
|
||||
frappe.db.get_value("Backup Manager", None, "dropbox_access_secret"))
|
||||
|
||||
dropbox_client = client.DropboxClient(sess)
|
||||
|
||||
# upload database
|
||||
backup = new_backup()
|
||||
filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
|
||||
upload_file_to_dropbox(filename, "/database", dropbox_client)
|
||||
|
||||
frappe.db.close()
|
||||
response = dropbox_client.metadata("/files")
|
||||
|
||||
# upload files to files folder
|
||||
did_not_upload = []
|
||||
error_log = []
|
||||
path = get_files_path()
|
||||
for filename in os.listdir(path):
|
||||
filename = cstr(filename)
|
||||
|
||||
if filename in ignore_list:
|
||||
continue
|
||||
|
||||
found = False
|
||||
filepath = os.path.join(path, filename)
|
||||
for file_metadata in response["contents"]:
|
||||
if os.path.basename(filepath) == os.path.basename(file_metadata["path"]) and os.stat(filepath).st_size == int(file_metadata["bytes"]):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
try:
|
||||
upload_file_to_dropbox(filepath, "/files", dropbox_client)
|
||||
except Exception:
|
||||
did_not_upload.append(filename)
|
||||
error_log.append(frappe.get_traceback())
|
||||
|
||||
frappe.connect()
|
||||
return did_not_upload, list(set(error_log))
|
||||
|
||||
def get_dropbox_session():
|
||||
try:
|
||||
from dropbox import session
|
||||
except:
|
||||
frappe.msgprint(_("Please install dropbox python module"), raise_exception=1)
|
||||
|
||||
if not (frappe.conf.dropbox_access_key or frappe.conf.dropbox_secret_key):
|
||||
frappe.throw(_("Please set Dropbox access keys in your site config"))
|
||||
|
||||
sess = session.DropboxSession(frappe.conf.dropbox_access_key, frappe.conf.dropbox_secret_key, "app_folder")
|
||||
return sess
|
||||
|
||||
def upload_file_to_dropbox(filename, folder, dropbox_client):
|
||||
from dropbox import rest
|
||||
size = os.stat(filename).st_size
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
# if max packet size reached, use chunked uploader
|
||||
max_packet_size = 4194304
|
||||
|
||||
if size > max_packet_size:
|
||||
uploader = dropbox_client.get_chunked_uploader(f, size)
|
||||
while uploader.offset < size:
|
||||
try:
|
||||
uploader.upload_chunked()
|
||||
uploader.finish(folder + "/" + os.path.basename(filename), overwrite=True)
|
||||
except rest.ErrorResponse:
|
||||
pass
|
||||
else:
|
||||
dropbox_client.put_file(folder + "/" + os.path.basename(filename), f, overwrite=True)
|
||||
|
||||
if __name__=="__main__":
|
||||
backup_to_dropbox()
|
||||
@@ -1,30 +0,0 @@
|
||||
<table class="table table-striped" style="max-width: 600px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%;">
|
||||
{{ __("Date") }}
|
||||
</th>
|
||||
<th style="width: 50%;">
|
||||
{{ __("File") }}
|
||||
</th>
|
||||
<th>
|
||||
{{ __("Size") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for (var i=0; i < files.length; i++) { %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ files[i][1] }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ files[i][0] }}" target="_blank">{{ files[i][0] }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ files[i][2] }}
|
||||
</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1,172 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# SETUP:
|
||||
# install pip install --upgrade google-api-python-client
|
||||
#
|
||||
# In Google API
|
||||
# - create new API project
|
||||
# - create new oauth2 client (create installed app type as google \
|
||||
# does not support subdomains)
|
||||
#
|
||||
# in conf.py, set oauth2 settings
|
||||
# gdrive_client_id
|
||||
# gdrive_client_secret
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import httplib2
|
||||
import os
|
||||
import mimetypes
|
||||
import frappe
|
||||
import oauth2client.client
|
||||
from frappe.utils import cstr
|
||||
from frappe import _
|
||||
from apiclient.discovery import build
|
||||
from apiclient.http import MediaFileUpload
|
||||
|
||||
# define log config for google drive api's log messages
|
||||
# basicConfig redirects log to stderr
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_gdrive_authorize_url():
|
||||
flow = get_gdrive_flow()
|
||||
authorize_url = flow.step1_get_authorize_url()
|
||||
return {
|
||||
"authorize_url": authorize_url,
|
||||
}
|
||||
|
||||
def upload_files(name, mimetype, service, folder_id):
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
file_name = os.path.basename(name)
|
||||
media_body = MediaFileUpload(name, mimetype=mimetype, resumable=True)
|
||||
body = {
|
||||
'title': file_name,
|
||||
'description': 'Backup File',
|
||||
'mimetype': mimetype,
|
||||
'parents': [{
|
||||
'kind': 'drive#filelink',
|
||||
'id': folder_id
|
||||
}]
|
||||
}
|
||||
request = service.files().insert(body=body, media_body=media_body)
|
||||
response = None
|
||||
while response is None:
|
||||
status, response = request.next_chunk()
|
||||
|
||||
def backup_to_gdrive():
|
||||
from frappe.utils.backups import new_backup
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
get_gdrive_flow()
|
||||
credentials_json = frappe.db.get_value("Backup Manager", None, "gdrive_credentials")
|
||||
credentials = oauth2client.client.Credentials.new_from_json(credentials_json)
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
drive_service = build('drive', 'v2', http=http)
|
||||
|
||||
# upload database
|
||||
backup = new_backup()
|
||||
path = os.path.join(frappe.local.site_path, "public", "backups")
|
||||
filename = os.path.join(path, os.path.basename(backup.backup_path_db))
|
||||
|
||||
# upload files to database folder
|
||||
upload_files(filename, 'application/x-gzip', drive_service,
|
||||
frappe.db.get_value("Backup Manager", None, "database_folder_id"))
|
||||
|
||||
# upload files to files folder
|
||||
did_not_upload = []
|
||||
error_log = []
|
||||
|
||||
files_folder_id = frappe.db.get_value("Backup Manager", None, "files_folder_id")
|
||||
|
||||
frappe.db.close()
|
||||
path = os.path.join(frappe.local.site_path, "public", "files")
|
||||
for filename in os.listdir(path):
|
||||
filename = cstr(filename)
|
||||
found = False
|
||||
filepath = os.path.join(path, filename)
|
||||
ext = filename.split('.')[-1]
|
||||
size = os.path.getsize(filepath)
|
||||
if ext == 'gz' or ext == 'gzip':
|
||||
mimetype = 'application/x-gzip'
|
||||
else:
|
||||
mimetype = mimetypes.types_map.get("." + ext) or "application/octet-stream"
|
||||
|
||||
#Compare Local File with Server File
|
||||
children = drive_service.children().list(folderId=files_folder_id).execute()
|
||||
for child in children.get('items', []):
|
||||
file = drive_service.files().get(fileId=child['id']).execute()
|
||||
if filename == file['title'] and size == int(file['fileSize']):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
try:
|
||||
upload_files(filepath, mimetype, drive_service, files_folder_id)
|
||||
except Exception, e:
|
||||
did_not_upload.append(filename)
|
||||
error_log.append(cstr(e))
|
||||
|
||||
frappe.connect()
|
||||
return did_not_upload, list(set(error_log))
|
||||
|
||||
def get_gdrive_flow():
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
from frappe import conf
|
||||
|
||||
if not "gdrive_client_id" in conf:
|
||||
frappe.throw(_("Please set Google Drive access keys in {0}"),format("site_config.json"))
|
||||
|
||||
flow = OAuth2WebServerFlow(conf.gdrive_client_id, conf.gdrive_client_secret,
|
||||
"https://www.googleapis.com/auth/drive", 'urn:ietf:wg:oauth:2.0:oob')
|
||||
return flow
|
||||
|
||||
@frappe.whitelist()
|
||||
def gdrive_callback(verification_code = None):
|
||||
flow = get_gdrive_flow()
|
||||
if verification_code:
|
||||
credentials = flow.step2_exchange(verification_code)
|
||||
allowed = 1
|
||||
|
||||
# make folders to save id
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
drive_service = build('drive', 'v2', http=http)
|
||||
erpnext_folder_id = create_erpnext_folder(drive_service)
|
||||
database_folder_id = create_folder('database', drive_service, erpnext_folder_id)
|
||||
files_folder_id = create_folder('files', drive_service, erpnext_folder_id)
|
||||
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "gdrive_access_allowed", allowed)
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "database_folder_id", database_folder_id)
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "files_folder_id", files_folder_id)
|
||||
final_credentials = credentials.to_json()
|
||||
frappe.db.set_value("Backup Manager", "Backup Manager", "gdrive_credentials", final_credentials)
|
||||
|
||||
frappe.msgprint(_("Updated"))
|
||||
|
||||
def create_erpnext_folder(service):
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
erpnext = {
|
||||
'title': 'erpnext',
|
||||
'mimeType': 'application/vnd.google-apps.folder'
|
||||
}
|
||||
erpnext = service.files().insert(body=erpnext).execute()
|
||||
return erpnext['id']
|
||||
|
||||
def create_folder(name, service, folder_id):
|
||||
database = {
|
||||
'title': name,
|
||||
'mimeType': 'application/vnd.google-apps.folder',
|
||||
'parents': [{
|
||||
'kind': 'drive#fileLink',
|
||||
'id': folder_id
|
||||
}]
|
||||
}
|
||||
database = service.files().insert(body=database).execute()
|
||||
return database['id']
|
||||
|
||||
if __name__=="__main__":
|
||||
backup_to_gdrive()
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
$.extend(cur_frm.cscript, {
|
||||
onload_post_render: function() {
|
||||
cur_frm.fields_dict.allow_dropbox_access.$input.addClass("btn-primary");
|
||||
|
||||
if(cur_frm.doc.__onload && cur_frm.doc.__onload.files) {
|
||||
$(frappe.render_template("backup_files_list", {files:cur_frm.doc.__onload.files}))
|
||||
.appendTo(cur_frm.fields_dict.current_backups.$wrapper.empty());
|
||||
}
|
||||
},
|
||||
refresh: function() {
|
||||
cur_frm.disable_save();
|
||||
},
|
||||
|
||||
validate_send_notifications_to: function() {
|
||||
if(!cur_frm.doc.send_notifications_to) {
|
||||
msgprint(__("Please specify") + ": " +
|
||||
__(frappe.meta.get_label(cur_frm.doctype, "send_notifications_to")));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
allow_dropbox_access: function() {
|
||||
if(cur_frm.cscript.validate_send_notifications_to()) {
|
||||
return frappe.call({
|
||||
method: "erpnext.setup.doctype.backup_manager.backup_dropbox.get_dropbox_authorize_url",
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
cur_frm.set_value("dropbox_access_secret", r.message.secret);
|
||||
cur_frm.set_value("dropbox_access_key", r.message.key);
|
||||
cur_frm.save(null, function() {
|
||||
window.open(r.message.url);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
allow_gdrive_access: function() {
|
||||
if(cur_frm.cscript.validate_send_notifications_to()) {
|
||||
return frappe.call({
|
||||
method: "erpnext.setup.doctype.backup_manager.backup_googledrive.get_gdrive_authorize_url",
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
window.open(r.message.authorize_url);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
validate_gdrive: function() {
|
||||
return frappe.call({
|
||||
method: "erpnext.setup.doctype.backup_manager.backup_googledrive.gdrive_callback",
|
||||
args: {
|
||||
verification_code: cur_frm.doc.verification_code
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
upload_backups_to_dropbox: function() {
|
||||
cur_frm.save();
|
||||
},
|
||||
|
||||
// upload_backups_to_gdrive: function() {
|
||||
// cur_frm.save();
|
||||
// },
|
||||
});
|
||||
@@ -1,482 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2013-04-30 12:58:38",
|
||||
"custom": 0,
|
||||
"description": "System for managing Backups",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "setup",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Download Backups",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "current_backups",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Current Backups",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "",
|
||||
"fieldname": "sync_with_dropbox",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sync with Dropbox",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "backup_right_now",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Backup Right Now",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "send_backups_to_dropbox",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Send Backups to Dropbox",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "send_backups_to_dropbox",
|
||||
"description": "Note: Backups and files are not deleted from Dropbox, you will have to delete them manually.",
|
||||
"fieldname": "upload_backups_to_dropbox",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Upload Backups to Dropbox",
|
||||
"no_copy": 0,
|
||||
"options": "Never\nWeekly\nDaily",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "send_backups_to_dropbox",
|
||||
"description": "Email ids separated by commas.",
|
||||
"fieldname": "send_notifications_to",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Send Notifications To",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "dropbox_access_key",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Dropbox Access Key",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "dropbox_access_secret",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Dropbox Access Secret",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "dropbox_access_allowed",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Dropbox Access Allowed",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "send_backups_to_dropbox",
|
||||
"fieldname": "allow_dropbox_access",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Allow Dropbox Access",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "Note: Backups and files are not deleted from Google Drive, you will have to delete them manually.",
|
||||
"fieldname": "sync_with_gdrive",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sync with Google Drive",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "upload_backups_to_gdrive",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Upload Backups to Google Drive",
|
||||
"no_copy": 0,
|
||||
"options": "Never\nDaily\nWeekly",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "allow_gdrive_access",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Allow Google Drive Access",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "verification_code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Enter Verification Code",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "validate_gdrive",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Validate",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "gdrive_access_allowed",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Google Drive Access Allowed",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "gdrive_credentials",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Credentials",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "database_folder_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Database Folder ID",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "files_folder_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Files Folder ID",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-cloud-upload",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-05-26 04:54:10.193573",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Backup Manager",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.utils import get_site_path, cint, split_emails
|
||||
from frappe.utils.data import convert_utc_to_user_timezone
|
||||
import os
|
||||
import datetime
|
||||
import frappe
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class BackupManager(Document):
|
||||
def onload(self):
|
||||
self.set_onload("files", get_files())
|
||||
|
||||
def get_files():
|
||||
def get_time(path):
|
||||
dt = os.path.getmtime(path)
|
||||
return convert_utc_to_user_timezone(datetime.datetime.utcfromtimestamp(dt)).strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
def get_size(path):
|
||||
size = os.path.getsize(path)
|
||||
if size > 1048576:
|
||||
return "{0:.1f}M".format(float(size) / 1048576)
|
||||
else:
|
||||
return "{0:.1f}K".format(float(size) / 1024)
|
||||
|
||||
path = get_site_path('private', 'backups')
|
||||
files = [x for x in os.listdir(path) if os.path.isfile(os.path.join(path, x))]
|
||||
files = [('/backups/' + _file,
|
||||
get_time(os.path.join(path, _file)),
|
||||
get_size(os.path.join(path, _file))) for _file in files]
|
||||
return files
|
||||
|
||||
def take_backups_daily():
|
||||
take_backups_if("Daily")
|
||||
|
||||
def take_backups_weekly():
|
||||
take_backups_if("Weekly")
|
||||
|
||||
def take_backups_if(freq):
|
||||
if cint(frappe.db.get_value("Backup Manager", None, "send_backups_to_dropbox")):
|
||||
if frappe.db.get_value("Backup Manager", None, "upload_backups_to_dropbox")==freq:
|
||||
take_backups_dropbox()
|
||||
|
||||
# if frappe.db.get_value("Backup Manager", None, "upload_backups_to_gdrive")==freq:
|
||||
# take_backups_gdrive()
|
||||
|
||||
@frappe.whitelist()
|
||||
def take_backups_dropbox():
|
||||
did_not_upload, error_log = [], []
|
||||
try:
|
||||
from erpnext.setup.doctype.backup_manager.backup_dropbox import backup_to_dropbox
|
||||
did_not_upload, error_log = backup_to_dropbox()
|
||||
if did_not_upload: raise Exception
|
||||
|
||||
send_email(True, "Dropbox")
|
||||
except Exception:
|
||||
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)]
|
||||
error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback())
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "Dropbox", error_message)
|
||||
|
||||
#backup to gdrive
|
||||
@frappe.whitelist()
|
||||
def take_backups_gdrive():
|
||||
did_not_upload, error_log = [], []
|
||||
try:
|
||||
from erpnext.setup.doctype.backup_manager.backup_googledrive import backup_to_gdrive
|
||||
did_not_upload, error_log = backup_to_gdrive()
|
||||
if did_not_upload: raise Exception
|
||||
|
||||
send_email(True, "Google Drive")
|
||||
except Exception:
|
||||
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)]
|
||||
error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback())
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "Google Drive", error_message)
|
||||
|
||||
def send_email(success, service_name, error_status=None):
|
||||
if success:
|
||||
subject = "Backup Upload Successful"
|
||||
message ="""<h3>Backup Uploaded Successfully</h3><p>Hi there, this is just to inform you
|
||||
that your backup was successfully uploaded to your %s account. So relax!</p>
|
||||
""" % service_name
|
||||
|
||||
else:
|
||||
subject = "[Warning] Backup Upload Failed"
|
||||
message ="""<h3>Backup Upload Failed</h3><p>Oops, your automated backup to %s
|
||||
failed.</p>
|
||||
<p>Error message: %s</p>
|
||||
<p>Please contact your system manager for more information.</p>
|
||||
""" % (service_name, error_status)
|
||||
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
|
||||
recipients = split_emails(frappe.db.get_value("Backup Manager", None, "send_notifications_to"))
|
||||
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||
@@ -46,13 +46,13 @@ class Company(Document):
|
||||
if for_company != self.name:
|
||||
frappe.throw(_("Account {0} does not belong to company: {1}")
|
||||
.format(self.get(field), self.name))
|
||||
|
||||
|
||||
def validate_currency(self):
|
||||
self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency")
|
||||
if self.default_currency and self.previous_default_currency and \
|
||||
self.default_currency != self.previous_default_currency and \
|
||||
self.check_if_transactions_exist():
|
||||
frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency."))
|
||||
frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency."))
|
||||
|
||||
def on_update(self):
|
||||
if not frappe.db.sql("""select name from tabAccount
|
||||
@@ -208,7 +208,7 @@ class Company(Document):
|
||||
|
||||
# clear default accounts, warehouses from item
|
||||
if warehouses:
|
||||
|
||||
|
||||
for f in ["default_warehouse", "website_warehouse"]:
|
||||
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
|
||||
% (f, f, ', '.join(['%s']*len(warehouses))), tuple(warehouses))
|
||||
@@ -257,3 +257,7 @@ def get_name_with_abbr(name, company):
|
||||
parts.append(company_abbr)
|
||||
|
||||
return " - ".join(parts)
|
||||
|
||||
def get_company_currency(company):
|
||||
return frappe.local_cache("company_currency", company,
|
||||
lambda: frappe.db.get_value("Company", company, "default_currency"))
|
||||
|
||||
@@ -84,8 +84,7 @@ class EmailDigest(Document):
|
||||
common_msg)
|
||||
if msg_for_this_receipient:
|
||||
frappe.sendmail(recipients=user_id,
|
||||
subject="[ERPNext] [{frequency} Digest] {name}".format(
|
||||
frequency=self.frequency, name=self.name),
|
||||
subject="{frequency} Digest".format(frequency=self.frequency),
|
||||
message=msg_for_this_receipient, bulk=True)
|
||||
|
||||
def get_digest_msg(self):
|
||||
|
||||
@@ -69,12 +69,13 @@ class NamingSeries(Document):
|
||||
|
||||
# update in property setter
|
||||
prop_dict = {'options': "\n".join(options), 'default': default}
|
||||
|
||||
for prop in prop_dict:
|
||||
ps_exists = frappe.db.sql("""SELECT name FROM `tabProperty Setter`
|
||||
WHERE doc_type = %s AND field_name = 'naming_series'
|
||||
AND property = %s""", (doctype, prop))
|
||||
ps_exists = frappe.db.get_value("Property Setter",
|
||||
{"field_name": 'naming_series', 'doc_type': doctype, 'property': prop})
|
||||
|
||||
if ps_exists:
|
||||
ps = frappe.get_doc('Property Setter', ps_exists[0][0])
|
||||
ps = frappe.get_doc('Property Setter', ps_exists)
|
||||
ps.value = prop_dict[prop]
|
||||
ps.save()
|
||||
else:
|
||||
@@ -180,6 +181,9 @@ def get_default_naming_series(doctype):
|
||||
naming_series = frappe.get_meta(doctype).get_field("naming_series").options or ""
|
||||
naming_series = naming_series.split("\n")
|
||||
out = naming_series[0] or (naming_series[1] if len(naming_series) > 1 else None)
|
||||
if out:
|
||||
|
||||
if not out:
|
||||
frappe.throw(_("Please set Naming Series for {0} via Setup > Settings > Naming Series").format(doctype),
|
||||
NamingSeriesNotSetError)
|
||||
else:
|
||||
return out
|
||||
|
||||
@@ -11,7 +11,7 @@ default_mail_footer = """<div style="padding: 7px; text-align: right; color: #88
|
||||
def after_install():
|
||||
frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
|
||||
set_single_defaults()
|
||||
frappe.db.set_default('desktop:home_page', 'setup-wizard')
|
||||
frappe.db.set_default('desktop:home_page', 'setup-wizard');
|
||||
feature_setup()
|
||||
from erpnext.setup.page.setup_wizard.setup_wizard import add_all_roles_to
|
||||
add_all_roles_to("Administrator")
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils.make_random import add_random_children, get_random
|
||||
from frappe.utils.make_random import add_random_children
|
||||
import frappe.utils
|
||||
import random
|
||||
|
||||
def make_sample_data():
|
||||
"""Create a few opportunities, quotes, material requests, issues, todos, projects
|
||||
@@ -13,11 +14,13 @@ def make_sample_data():
|
||||
|
||||
selling_items = frappe.get_all("Item", filters = {"is_sales_item": 1})
|
||||
buying_items = frappe.get_all("Item", filters = {"is_purchase_item": 1})
|
||||
|
||||
if selling_items:
|
||||
customers = frappe.get_all("Customer")
|
||||
|
||||
if selling_items and customers:
|
||||
for i in range(3):
|
||||
make_opportunity(selling_items)
|
||||
make_quote(selling_items)
|
||||
customer = random.choice(customers).name
|
||||
make_opportunity(selling_items, customer)
|
||||
make_quote(selling_items, customer)
|
||||
|
||||
make_projects()
|
||||
|
||||
@@ -26,11 +29,11 @@ def make_sample_data():
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def make_opportunity(selling_items):
|
||||
def make_opportunity(selling_items, customer):
|
||||
b = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"enquiry_from": "Customer",
|
||||
"customer": get_random("Customer"),
|
||||
"customer": customer,
|
||||
"enquiry_type": "Sales",
|
||||
"with_items": 1
|
||||
})
|
||||
@@ -44,11 +47,11 @@ def make_opportunity(selling_items):
|
||||
|
||||
b.add_comment("This is a dummy record")
|
||||
|
||||
def make_quote(selling_items):
|
||||
def make_quote(selling_items, customer):
|
||||
qtn = frappe.get_doc({
|
||||
"doctype": "Quotation",
|
||||
"quotation_to": "Customer",
|
||||
"customer": get_random("Customer"),
|
||||
"customer": customer,
|
||||
"order_type": "Sales"
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ frappe.provide("erpnext.wiz");
|
||||
|
||||
frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
|
||||
if(sys_defaults.company) {
|
||||
frappe.set_route("desktop");
|
||||
frappe.set_route("desk-home");
|
||||
return;
|
||||
}
|
||||
$(".navbar:first").toggle(false);
|
||||
|
||||
@@ -92,8 +92,10 @@ def setup_account(args=None):
|
||||
if args.get("add_sample_data"):
|
||||
try:
|
||||
make_sample_data()
|
||||
frappe.clear_cache()
|
||||
except FiscalYearError:
|
||||
pass
|
||||
|
||||
except:
|
||||
if args:
|
||||
traceback = frappe.get_traceback()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def get_notification_config():
|
||||
return { "for_doctype":
|
||||
@@ -15,7 +14,11 @@ def get_notification_config():
|
||||
"Contact": {"status": "Open"},
|
||||
"Opportunity": {"status": "Open"},
|
||||
"Quotation": {"docstatus": 0},
|
||||
"Sales Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Sales Order": {
|
||||
"status": ("!=", "Stopped"),
|
||||
"status": ("!=", "Completed"),
|
||||
"docstatus": ("<", 2)
|
||||
},
|
||||
"Journal Entry": {"docstatus": 0},
|
||||
"Sales Invoice": { "outstanding_amount": (">", 0), "docstatus": ("<", 2) },
|
||||
"Purchase Invoice": {"docstatus": 0},
|
||||
@@ -26,7 +29,11 @@ def get_notification_config():
|
||||
"Delivery Note": {"docstatus": 0},
|
||||
"Stock Entry": {"docstatus": 0},
|
||||
"Material Request": {"docstatus": 0},
|
||||
"Purchase Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Purchase Order": {
|
||||
"status": ("!=", "Completed"),
|
||||
"status": ("!=", "Stopped"),
|
||||
"docstatus": ("<", 2)
|
||||
},
|
||||
"Production Order": { "status": "In Process" },
|
||||
"BOM": {"docstatus": 0},
|
||||
"Timesheet": {"docstatus": 0},
|
||||
|
||||
@@ -699,7 +699,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -2438,7 +2438,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-23 09:54:33.751001",
|
||||
"modified": "2015-09-30 08:52:57.917732",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -561,7 +561,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "items",
|
||||
@@ -2076,7 +2076,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-11 12:20:13.894374",
|
||||
"modified": "2015-09-30 08:52:54.964591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -10,6 +10,7 @@ from frappe import _
|
||||
import frappe.defaults
|
||||
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@@ -307,9 +308,9 @@ class PurchaseReceipt(BuyingController):
|
||||
val_rate_db_precision = 6 if cint(d.precision("valuation_rate")) <= 6 else 9
|
||||
|
||||
# warehouse account
|
||||
stock_value_diff = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty)
|
||||
stock_value_diff = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty)
|
||||
* flt(d.conversion_factor), d.precision("base_net_amount"))
|
||||
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": warehouse_account[d.warehouse]["name"],
|
||||
"against": stock_rbnb,
|
||||
@@ -319,7 +320,7 @@ class PurchaseReceipt(BuyingController):
|
||||
}, warehouse_account[d.warehouse]["account_currency"]))
|
||||
|
||||
# stock received but not billed
|
||||
stock_rbnb_currency = frappe.db.get_value("Account", stock_rbnb, "account_currency")
|
||||
stock_rbnb_currency = get_account_currency(stock_rbnb)
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": stock_rbnb,
|
||||
"against": warehouse_account[d.warehouse]["name"],
|
||||
|
||||
@@ -241,7 +241,6 @@ class StockEntry(StockController):
|
||||
self.set_basic_rate(force)
|
||||
self.distribute_additional_costs()
|
||||
self.update_valuation_rate()
|
||||
self.validate_valuation_rate()
|
||||
self.set_total_incoming_outgoing_value()
|
||||
self.set_total_amount()
|
||||
|
||||
@@ -299,19 +298,6 @@ class StockEntry(StockController):
|
||||
d.valuation_rate = flt(flt(d.basic_rate) + flt(d.additional_cost) / flt(d.transfer_qty),
|
||||
d.precision("valuation_rate"))
|
||||
|
||||
def validate_valuation_rate(self):
|
||||
if self.purpose in ["Manufacture", "Repack"]:
|
||||
valuation_at_source, valuation_at_target = 0, 0
|
||||
for d in self.get("items"):
|
||||
if d.s_warehouse and not d.t_warehouse:
|
||||
valuation_at_source += flt(d.amount)
|
||||
if d.t_warehouse and not d.s_warehouse:
|
||||
valuation_at_target += flt(d.amount)
|
||||
|
||||
if valuation_at_target + 0.001 < valuation_at_source:
|
||||
frappe.throw(_("Total valuation ({0}) for manufactured or repacked item(s) can not be less than total valuation of raw materials ({1})")
|
||||
.format(valuation_at_target, valuation_at_source))
|
||||
|
||||
def set_total_incoming_outgoing_value(self):
|
||||
self.total_incoming_value = self.total_outgoing_value = 0.0
|
||||
for d in self.get("items"):
|
||||
|
||||
@@ -286,7 +286,7 @@
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -309,7 +309,7 @@
|
||||
"options": "image",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -909,7 +909,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-08-27 03:30:49.875635",
|
||||
"modified": "2015-09-30 02:11:55.081524",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
||||
@@ -200,7 +200,8 @@ def get_default_expense_account(args, item):
|
||||
def get_default_cost_center(args, item):
|
||||
return (frappe.db.get_value("Project", args.get("project_name"), "cost_center")
|
||||
or (item.selling_cost_center if args.get("transaction_type") == "selling" else item.buying_cost_center)
|
||||
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center"))
|
||||
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
|
||||
or args.get("cost_center"))
|
||||
|
||||
def get_price_list_rate(args, item_doc, out):
|
||||
meta = frappe.get_meta(args.parenttype)
|
||||
@@ -345,8 +346,7 @@ def get_conversion_factor(item_code, uom):
|
||||
variant_of = frappe.db.get_value("Item", item_code, "variant_of")
|
||||
filters = {"parent": item_code, "uom": uom}
|
||||
if variant_of:
|
||||
filters = {"parent": ("in", (item_code, variant_of))}
|
||||
|
||||
filters["parent"] = ("in", (item_code, variant_of))
|
||||
return {"conversion_factor": frappe.db.get_value("UOM Conversion Detail",
|
||||
filters, "conversion_factor")}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-02-22 18:01:55",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2015-06-10 15:52:49.492144",
|
||||
"modified": "2015-10-06 12:43:37.547654",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Ordered Items To Be Delivered",
|
||||
"owner": "Administrator",
|
||||
"query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project_name` as \"Project\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order`.`delivery_date` as \"Expected Delivery Date:Date:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order`, `tabSales Order Item`, `tabBin`\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status != \"Stopped\"\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\n and `tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse\norder by `tabSales Order`.transaction_date asc",
|
||||
"query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project_name` as \"Project\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order`.`delivery_date` as \"Expected Delivery Date:Date:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status != \"Stopped\"\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc",
|
||||
"ref_doctype": "Delivery Note",
|
||||
"report_name": "Ordered Items To Be Delivered",
|
||||
"report_type": "Query Report"
|
||||
|
||||
@@ -93,14 +93,14 @@ def get_item_warehouse_map(filters):
|
||||
qty_dict.opening_qty += qty_diff
|
||||
qty_dict.opening_val += value_diff
|
||||
elif d.posting_date >= getdate(filters["from_date"]) and d.posting_date <= getdate(filters["to_date"]):
|
||||
qty_dict.val_rate = d.valuation_rate
|
||||
if qty_diff > 0:
|
||||
qty_dict.in_qty += qty_diff
|
||||
qty_dict.in_val += value_diff
|
||||
else:
|
||||
qty_dict.out_qty += abs(qty_diff)
|
||||
qty_dict.out_val += abs(value_diff)
|
||||
|
||||
|
||||
qty_dict.val_rate = d.valuation_rate
|
||||
qty_dict.bal_qty += qty_diff
|
||||
qty_dict.bal_val += value_diff
|
||||
|
||||
|
||||
@@ -597,7 +597,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-09-07 15:51:26",
|
||||
"modified": "2015-09-24 06:54:33.383186",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Issue",
|
||||
@@ -605,7 +605,7 @@
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
frappe.listview_settings['Issue'] = {
|
||||
colwidths: {"subject": 6},
|
||||
onload: function(listview) {
|
||||
frappe.route_options = {
|
||||
"status": "Open"
|
||||
};
|
||||
|
||||
var method = "erpnext.support.doctype.issue.issue.set_multiple_status";
|
||||
|
||||
listview.page.add_menu_item(__("Set as Open"), function() {
|
||||
|
||||
@@ -14,5 +14,5 @@ def get_context(context):
|
||||
|
||||
context.parents = frappe.form_dict.parents
|
||||
|
||||
if not context.doc.has_permission("read"):
|
||||
if not context.doc.has_website_permission("read"):
|
||||
frappe.throw(_("Not Permitted"), frappe.PermissionError)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
DocType: Account,Accounts,དངུལ་རྩིས།
|
||||
DocType: Pricing Rule,Buying,ཉོ་བ།
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ DocType: Department,Department,Département
|
||||
DocType: Purchase Order,% Billed,Facturé%
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +44,Exchange Rate must be same as {0} {1} ({2}),Taux de change doit être le même que {0} {1} ({2})
|
||||
DocType: Sales Invoice,Customer Name,Nom du client
|
||||
DocType: Features Setup,"All export related fields like currency, conversion rate, export total, export grand total etc are available in Delivery Note, POS, Quotation, Sales Invoice, Sales Order etc.","Tous les champs liés à l'exportation comme monnaie , taux de conversion , l'exportation totale , l'exportation totale grandiose etc sont disponibles dans la note de livraison , POS , offre , facture de vente , Sales Order etc"
|
||||
DocType: Features Setup,"All export related fields like currency, conversion rate, export total, export grand total etc are available in Delivery Note, POS, Quotation, Sales Invoice, Sales Order etc.","Tous les champs liés à l'exportation comme monnaie , taux de conversion , l'exportation totale , l'exportation totale grandiose etc sont disponibles dans Bon de livraison , Point de Vente , Devis, Factures, Bons de commandes etc"
|
||||
DocType: Account,Heads (or groups) against which Accounting Entries are made and balances are maintained.,Chefs (ou groupes) contre lequel les entrées comptables sont faites et les soldes sont maintenus.
|
||||
apps/erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py +154,Outstanding for {0} cannot be less than zero ({1}),Participation pour les employés {0} est déjà marqué
|
||||
DocType: Manufacturing Settings,Default 10 mins,Par défaut 10 minutes
|
||||
@@ -337,7 +337,7 @@ apps/erpnext/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet
|
||||
DocType: Purchase Invoice,"Enter email id separated by commas, invoice will be mailed automatically on particular date","Entrez Identifiant courriels séparé par des virgules, la facture sera envoyée automatiquement à la date particulière"
|
||||
DocType: Employee,Company Email,E-mail entreprise
|
||||
DocType: Workflow State,Refresh,Rafraîchir
|
||||
DocType: Features Setup,"All import related fields like currency, conversion rate, import total, import grand total etc are available in Purchase Receipt, Supplier Quotation, Purchase Invoice, Purchase Order etc.","Tous les champs importation connexes comme monnaie , taux de conversion , totale d'importation , importation grande etc totale sont disponibles en Achat réception , Fournisseur d'offre , facture d'achat , bon de commande , etc"
|
||||
DocType: Features Setup,"All import related fields like currency, conversion rate, import total, import grand total etc are available in Purchase Receipt, Supplier Quotation, Purchase Invoice, Purchase Order etc.","Tous les champs importation connexes comme monnaie , taux de conversion , total d'importation , importation grande etc totale sont disponibles en Achat réception , Devis fournisseur , Facture d'achat , bon de commande , etc"
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.js +29,This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set,Cet article est un modèle et ne peut être utilisé dans les transactions. Attributs d'élément seront copiés dans les variantes moins 'No Copy »est réglé
|
||||
apps/erpnext/erpnext/selling/report/customers_not_buying_since_long_time/customers_not_buying_since_long_time.py +69,Total Order Considered,Total de la commande Considéré
|
||||
apps/erpnext/erpnext/config/hr.py +110,"Employee designation (e.g. CEO, Director etc.).",Vous devez enregistrer le formulaire avant de procéder
|
||||
@@ -392,7 +392,7 @@ apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +35,Tube bea
|
||||
apps/erpnext/erpnext/manufacturing/doctype/workstation/workstation.py +79,Workstation is closed on the following dates as per Holiday List: {0},Workstation est fermé aux dates suivantes selon la liste de vacances: {0}
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.js +627,Make Maint. Schedule,Assurez- Maint . calendrier
|
||||
DocType: Employee,Single,Unique
|
||||
DocType: Issue,Attachment,Attachement
|
||||
DocType: Issue,Attachment,Pièce jointe
|
||||
apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center.py +29,Budget cannot be set for Group Cost Center,Budget ne peut pas être réglé pour le centre de coûts du Groupe
|
||||
DocType: Account,Cost of Goods Sold,Montant payé + Write Off montant ne peut être supérieur à Total
|
||||
DocType: Purchase Invoice,Yearly,Annuel
|
||||
@@ -1901,7 +1901,7 @@ apps/erpnext/erpnext/stock/get_item_details.py +103,No Item with Barcode {0},Bon
|
||||
apps/erpnext/erpnext/stock/doctype/packing_slip/packing_slip.js +51,Case No. cannot be 0,Cas n ° ne peut pas être 0
|
||||
DocType: Features Setup,If you have Sales Team and Sale Partners (Channel Partners) they can be tagged and maintain their contribution in the sales activity,"Si vous avez équipe de vente et Partenaires Vente (Channel Partners), ils peuvent être marqués et maintenir leur contribution à l'activité commerciale"
|
||||
DocType: Item,Show a slideshow at the top of the page,Afficher un diaporama en haut de la page
|
||||
DocType: Item,"Allow in Sales Order of type ""Service""",Permettre à la commande client de type "service"
|
||||
DocType: Item,"Allow in Sales Order of type ""Service""","Autoriser dans les bon de commandes de type ""Service"""
|
||||
apps/erpnext/erpnext/setup/doctype/company/company.py +77,Stores,Magasins
|
||||
DocType: Time Log,Projects Manager,Gestionnaire de projets
|
||||
DocType: Serial No,Delivery Time,L'heure de la livraison
|
||||
@@ -2427,7 +2427,7 @@ apps/erpnext/erpnext/shopping_cart/utils.py +43,Orders,Commandes
|
||||
DocType: Leave Control Panel,Employee Type,Type de contrat
|
||||
DocType: Employee Leave Approver,Leave Approver,Laisser approbateur
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +69,Swaging,Emboutissage
|
||||
DocType: Expense Claim,"A user with ""Expense Approver"" role","Un utilisateur avec le rôle ""frais approbateur"""
|
||||
DocType: Expense Claim,"A user with ""Expense Approver"" role","Un utilisateur avec le rôle ""Autorise les dépenses"""
|
||||
,Issued Items Against Production Order,Articles émis contre un ordre de fabrication
|
||||
DocType: Pricing Rule,Purchase Manager,Directeur des achats
|
||||
DocType: Payment Tool,Payment Tool,Paiement Outil
|
||||
@@ -3917,7 +3917,7 @@ apps/erpnext/erpnext/setup/page/setup_wizard/setup_wizard.js +528,Purchaser,Ache
|
||||
apps/erpnext/erpnext/hr/doctype/salary_structure/salary_structure.py +81,Net pay cannot be negative,Landed Cost correctement mis à jour
|
||||
apps/erpnext/erpnext/accounts/doctype/payment_tool/payment_tool.py +70,Please enter the Against Vouchers manually,Se il vous plaît entrer le contre Chèques manuellement
|
||||
DocType: SMS Settings,Static Parameters,Paramètres statiques
|
||||
DocType: Purchase Order,Advance Paid,Payé Advance
|
||||
DocType: Purchase Order,Advance Paid,Acompte payée
|
||||
DocType: Item,Item Tax,Taxe sur l'Article
|
||||
DocType: Expense Claim,Employees Email Id,Identifiants email des employés
|
||||
apps/erpnext/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +159,Current Liabilities,Le solde doit être
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user