Merge branch 'develop'
This commit is contained in:
commit
185f488c51
@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '6.3.2'
|
||||
__version__ = '6.4.0'
|
||||
|
@ -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,14 @@ 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"""
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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":
|
||||
|
@ -6,7 +6,8 @@ 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.accounts.party import get_party_account_currency
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
|
||||
|
||||
@ -146,7 +147,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 +170,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 +196,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 +212,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
|
||||
@ -609,8 +609,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 +660,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 +779,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 +826,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:
|
||||
|
@ -70,7 +70,7 @@ class PaymentReconciliation(Document):
|
||||
non_reconciled_invoices = []
|
||||
dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
|
||||
cond = self.check_condition(dr_or_cr)
|
||||
|
||||
|
||||
invoice_list = frappe.db.sql("""
|
||||
select
|
||||
voucher_no, voucher_type, posting_date,
|
||||
@ -141,7 +141,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def reconcile(self, args):
|
||||
for e in self.get('payments'):
|
||||
if " | " in e.invoice_number:
|
||||
if e.invoice_number and " | " in e.invoice_number:
|
||||
e.invoice_type, e.invoice_number = e.invoice_number.split(" | ")
|
||||
|
||||
self.get_invoice_entries()
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
@ -209,7 +209,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 = {}
|
||||
@ -55,7 +56,7 @@ def set_account_currency(filters):
|
||||
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,
|
||||
"party": filters.party, "company": filters.company}, "account_currency")
|
||||
|
@ -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
|
||||
|
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"
|
||||
}]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
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,8 +426,7 @@ 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 != self.company_currency and self.currency != party_account_currency:
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
|
@ -207,10 +207,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
|
||||
|
@ -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.0"
|
||||
github_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -211,3 +211,4 @@ 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
|
||||
|
@ -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
|
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() {
|
||||
|
@ -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...'));
|
||||
});
|
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):
|
||||
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"
|
||||
|
@ -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 +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:
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
@ -772,7 +772,7 @@ apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +15,Permanen
|
||||
DocType: Sales Order Item,Projected Qty,Qtde. Projetada
|
||||
DocType: Sales Invoice,Payment Due Date,Data de Vencimento
|
||||
DocType: Newsletter,Newsletter Manager,Boletim Gerente
|
||||
apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py +95,'Opening','Aberto'
|
||||
apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py +95,'Opening','Abrindo'
|
||||
DocType: Notification Control,Delivery Note Message,Mensagem da Guia de Remessa
|
||||
DocType: Expense Claim,Expenses,Despesas
|
||||
,Purchase Receipt Trends,Compra Trends Recibo
|
||||
@ -1370,7 +1370,7 @@ DocType: HR Settings,Stop Birthday Reminders,Parar Aniversário Lembretes
|
||||
DocType: SMS Center,Receiver List,Lista de recebedores
|
||||
DocType: Payment Tool Detail,Payment Amount,Valor do Pagamento
|
||||
apps/erpnext/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +46,Consumed Amount,Quantidade consumida
|
||||
sites/assets/js/erpnext.min.js +49,{0} View,{0} Vista
|
||||
sites/assets/js/erpnext.min.js +49,{0} View,{0} Visão
|
||||
DocType: Salary Structure Deduction,Salary Structure Deduction,Dedução da Estrutura Salarial
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +157,Selective laser sintering,Seletiva sinterização a laser
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +153,Unit of Measure {0} has been entered more than once in Conversion Factor Table,Unidade de Medida {0} foi inserida mais de uma vez na Tabela de Conversão de Fator
|
||||
@ -2152,7 +2152,7 @@ DocType: Landed Cost Voucher,Purchase Receipt Items,Itens do Recibo de Compra
|
||||
apps/erpnext/erpnext/config/learn.py +21,Customizing Forms,Formas de personalização
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +61,Cutting,Corte
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +68,Flattening,Achatamento
|
||||
DocType: Account,Income Account,Conta de Renda
|
||||
DocType: Account,Income Account,Conta de Receitas
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +21,Molding,Modelagem
|
||||
DocType: Stock Reconciliation Item,Current Qty,Qtde atual
|
||||
DocType: BOM Item,"See ""Rate Of Materials Based On"" in Costing Section",Consulte "taxa de materiais baseados em" no Custeio Seção
|
||||
@ -3548,7 +3548,7 @@ DocType: Time Log,For Manufacturing,Para Manufacturing
|
||||
apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py +124,Totals,Totais
|
||||
DocType: BOM,Manufacturing,Fabricação
|
||||
,Ordered Items To Be Delivered,Itens encomendados a serem entregues
|
||||
DocType: Account,Income,renda
|
||||
DocType: Account,Income,Receitas
|
||||
,Setup Wizard,Assistente de Configuração
|
||||
DocType: Industry Type,Industry Type,Tipo de indústria
|
||||
apps/erpnext/erpnext/templates/includes/cart.js +265,Something went wrong!,Algo deu errado!
|
||||
|
|
@ -330,7 +330,7 @@ DocType: Backup Manager,Allow Dropbox Access,Разрешить Dropbox Access
|
||||
apps/erpnext/erpnext/config/learn.py +72,Setting up Taxes,Настройка Налоги
|
||||
DocType: Communication,Support Manager,Поддержка менеджер
|
||||
apps/erpnext/erpnext/accounts/utils.py +182,Payment Entry has been modified after you pulled it. Please pull it again.,"Оплата запись была изменена после того, как вытащил его. Пожалуйста, вытащить его снова."
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +195,{0} entered twice in Item Tax,{0} вводится дважды в пункт налоге
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +195,{0} entered twice in Item Tax,{0} вводится дважды в пункт налог
|
||||
DocType: Workstation,Rent Cost,Стоимость аренды
|
||||
DocType: Manage Variants Item,Variant Attributes,Вариант Атрибуты
|
||||
apps/erpnext/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +73,Please select month and year,"Пожалуйста, выберите месяц и год"
|
||||
@ -346,7 +346,7 @@ DocType: Sales Invoice,Rate at which Customer Currency is converted to customer'
|
||||
DocType: Features Setup,"Available in BOM, Delivery Note, Purchase Invoice, Production Order, Purchase Order, Purchase Receipt, Sales Invoice, Sales Order, Stock Entry, Timesheet","Доступный в спецификации, накладной, счете-фактуре, производственного заказа, заказа на поставку, покупка получение, счет-фактура, заказ клиента, фондовой въезда, расписания"
|
||||
DocType: Item Tax,Tax Rate,Размер налога
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_order/purchase_order.js +504,Select Item,Выбрать пункт
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py +208,{0} {1} status is Stopped,{0} {1} положение остановлен
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py +208,{0} {1} status is Stopped,{0} {1} статус остановлен
|
||||
apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +143,"Item: {0} managed batch-wise, can not be reconciled using \
|
||||
Stock Reconciliation, instead use Stock Entry","Пункт: {0} удалось порционно, не могут быть согласованы с помощью \
|
||||
со примирения, вместо этого использовать со запись"
|
||||
@ -385,7 +385,7 @@ apps/erpnext/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +57,"If multi
|
||||
,Purchase Register,Покупка Становиться на учет
|
||||
DocType: Landed Cost Item,Applicable Charges,Взимаемых платежах
|
||||
DocType: Workstation,Consumable Cost,Расходные Стоимость
|
||||
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py +153,{0} ({1}) must have role 'Leave Approver',"{0} ({1}) должен иметь роль ""Утверждающего Убытки"""
|
||||
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py +153,{0} ({1}) must have role 'Leave Approver',"{0} ({1}) должен иметь роль ""Подтверждающего Отсутствие"""
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/install_fixtures.py +39,Medical,Медицинский
|
||||
apps/erpnext/erpnext/crm/doctype/opportunity/opportunity.js +124,Reason for losing,Причина потери
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +35,Tube beading,Труба бисером
|
||||
@ -461,7 +461,7 @@ sites/assets/js/erpnext.min.js +54,Please select Company,"Пожалуйста,
|
||||
DocType: Stock Entry,Difference Account,Счет разницы
|
||||
apps/erpnext/erpnext/projects/doctype/task/task.py +44,Cannot close task as its dependant task {0} is not closed.,"Невозможно закрыть задача, как ее зависит задача {0} не закрыт."
|
||||
apps/erpnext/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +298,Please enter Warehouse for which Material Request will be raised,"Пожалуйста, введите Склад для которых Материал Запрос будет поднят"
|
||||
DocType: Production Order,Additional Operating Cost,Дополнительная Эксплуатационные расходы
|
||||
DocType: Production Order,Additional Operating Cost,Дополнительные Эксплуатационные расходы
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/industry_type.py +19,Cosmetics,Косметика
|
||||
DocType: DocField,Type,Тип
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +272,"To merge, following properties must be same for both items","Чтобы объединить, следующие свойства должны быть одинаковыми для обоих пунктов"
|
||||
@ -490,8 +490,8 @@ DocType: Variant Attribute,Variant Attribute,Вариант Атрибут
|
||||
DocType: Company,Delete Company Transactions,Удалить Сделки Компания
|
||||
apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +84,Item {0} is not Purchase Item,Пункт {0} не Приобретите товар
|
||||
apps/erpnext/erpnext/controllers/recurring_document.py +189,"{0} is an invalid email address in 'Notification \
|
||||
Email Address'","{0} неправильный адрес электронной почты в ""\
|
||||
Уведомление адресом электронной почты"""
|
||||
Email Address'","{0} неправильный адрес электронной почты в ""
|
||||
Уведомление \ адрес электронной почты"""
|
||||
apps/erpnext/erpnext/buying/doctype/supplier/supplier.js +44,Total Billing This Year:,Всего счетов в этом году:
|
||||
DocType: Purchase Receipt,Add / Edit Taxes and Charges,Добавить / Изменить Налоги и сборы
|
||||
DocType: Purchase Invoice,Supplier Invoice No,Поставщик Счет Нет
|
||||
@ -542,7 +542,7 @@ apps/erpnext/erpnext/accounts/page/financial_analytics/financial_analytics.js +5
|
||||
apps/erpnext/erpnext/accounts/utils.py +186,Allocated amount can not be negative,Выделенные сумма не может быть отрицательным
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +122,Tumbling,Акробатика
|
||||
DocType: Purchase Order Item,Billed Amt,Счетов выдано кол-во
|
||||
DocType: Warehouse,A logical Warehouse against which stock entries are made.,"Логично Склад, на котором фондовые записи сделаны."
|
||||
DocType: Warehouse,A logical Warehouse against which stock entries are made.,"Логика Склада,по которому сделаны складские записи"
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +110,Reference No & Reference Date is required for {0},Ссылка № & Ссылка Дата необходим для {0}
|
||||
DocType: Event,Wednesday,Среда
|
||||
DocType: Sales Invoice,Customer's Vendor,Производитель Клиентам
|
||||
@ -593,7 +593,7 @@ apps/erpnext/erpnext/config/buying.py +28,Purchase Orders given to Suppliers.,"
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/industry_type.py +42,Publishing,Публикация
|
||||
DocType: Activity Cost,Projects User,Проекты Пользователь
|
||||
apps/erpnext/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +41,Consumed,Потребляемая
|
||||
apps/erpnext/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +187,{0}: {1} not found in Invoice Details table,{0} {1} не найден в счете-фактуре таблице
|
||||
apps/erpnext/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +187,{0}: {1} not found in Invoice Details table,{0} {1} не найден в таблице счета
|
||||
DocType: Company,Round Off Cost Center,Округление Стоимость центр
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py +189,Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Техническое обслуживание Посетить {0} должно быть отменено до отмены этого заказ клиента
|
||||
DocType: Material Request,Material Transfer,О передаче материала
|
||||
@ -737,7 +737,7 @@ DocType: Process Payroll,Send Email,Отправить e-mail
|
||||
apps/erpnext/erpnext/buying/doctype/supplier/supplier.py +85,No Permission,Нет разрешения
|
||||
DocType: Company,Default Bank Account,По умолчанию Банковский счет
|
||||
apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py +43,"To filter based on Party, select Party Type first","Чтобы отфильтровать на основе партии, выберите партия первого типа"
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +49,'Update Stock' can not be checked because items are not delivered via {0},""Обновление со" не могут быть проверены, так как элементы не поставляется через {0}"
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +49,'Update Stock' can not be checked because items are not delivered via {0},"""Обновления Склада"" не могут быть проверены, так как позиция не поставляется через {0}"
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/setup_wizard.js +626,Nos,кол-во
|
||||
DocType: Item,Items with higher weightage will be shown higher,"Элементы с более высокой weightage будет показано выше,"
|
||||
DocType: Bank Reconciliation Detail,Bank Reconciliation Detail,Банковская сверка подробно
|
||||
@ -759,7 +759,7 @@ DocType: Email Digest,Email Digest Settings,Email Дайджест Настро
|
||||
apps/erpnext/erpnext/config/support.py +13,Support queries from customers.,Поддержка запросов от клиентов.
|
||||
DocType: Bin,Moving Average Rate,Moving Average Rate
|
||||
DocType: Production Planning Tool,Select Items,Выберите товары
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +292,{0} against Bill {1} dated {2},{0} против Счета {1} от {2}
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +292,{0} against Bill {1} dated {2},{0} по Счету {1} от {2}
|
||||
DocType: Comment,Reference Name,Ссылка Имя
|
||||
DocType: Maintenance Visit,Completion Status,Статус завершения
|
||||
DocType: Production Order,Target Warehouse,Целевая Склад
|
||||
@ -1372,7 +1372,7 @@ DocType: HR Settings,Stop Birthday Reminders,Стоп День рождения
|
||||
DocType: SMS Center,Receiver List,Приемник Список
|
||||
DocType: Payment Tool Detail,Payment Amount,Сумма платежа
|
||||
apps/erpnext/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +46,Consumed Amount,Израсходованное количество
|
||||
sites/assets/js/erpnext.min.js +49,{0} View,{0} Посмотреть
|
||||
sites/assets/js/erpnext.min.js +49,{0} View,{0} Просмотр
|
||||
DocType: Salary Structure Deduction,Salary Structure Deduction,Зарплата Структура Вычет
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +157,Selective laser sintering,Селективный лазерного спекания
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +153,Unit of Measure {0} has been entered more than once in Conversion Factor Table,Единица измерения {0} был введен более чем один раз в таблицу преобразования Factor
|
||||
@ -1515,7 +1515,7 @@ apps/erpnext/erpnext/shopping_cart/utils.py +47,Addresses,Адреса
|
||||
DocType: Communication,Received,Получено
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +155,Against Journal Entry {0} does not have any unmatched {1} entry,Против Запись в журнале {0} не имеет никакого непревзойденную {1} запись
|
||||
apps/erpnext/erpnext/stock/doctype/serial_no/serial_no.py +216,Duplicate Serial No entered for Item {0},Дубликат Серийный номер вводится для Пункт {0}
|
||||
DocType: Shipping Rule Condition,A condition for a Shipping Rule,Состояние для правила судоходства
|
||||
DocType: Shipping Rule Condition,A condition for a Shipping Rule,Условия для правил перевозки
|
||||
apps/erpnext/erpnext/manufacturing/doctype/production_order/production_order.py +326,Item is not allowed to have Production Order.,Деталь не разрешается иметь производственного заказа.
|
||||
apps/erpnext/erpnext/accounts/page/accounts_browser/accounts_browser.js +205,"Name of new Account. Note: Please don't create accounts for Customers and Suppliers, they are created automatically from the Customer and Supplier master","Имя нового счета. Примечание: Пожалуйста, не создавать учетные записи для клиентов и поставщиков, они создаются автоматически от Заказчика и поставщика оригиналов"
|
||||
DocType: DocField,Attach Image,Прикрепить изображение
|
||||
@ -1989,7 +1989,7 @@ apps/erpnext/erpnext/manufacturing/doctype/bom/bom.js +92,You can not change rat
|
||||
DocType: Employee,Previous Work Experience,Предыдущий опыт работы
|
||||
DocType: Stock Entry,For Quantity,Для Количество
|
||||
apps/erpnext/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +153,Please enter Planned Qty for Item {0} at row {1},"Пожалуйста, введите Запланированное Количество по пункту {0} в строке {1}"
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +217,{0} {1} is not submitted,{0} {1} не отправлен
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +217,{0} {1} is not submitted,{0} {1} не проведен
|
||||
apps/erpnext/erpnext/config/stock.py +13,Requests for items.,Запросы на предметы.
|
||||
DocType: Production Planning Tool,Separate production order will be created for each finished good item.,Отдельный производственный заказ будет создан для каждого готового хорошего пункта.
|
||||
DocType: Email Digest,New Communications,Новые Коммуникации
|
||||
@ -2034,7 +2034,7 @@ DocType: Sales Partner,A third party distributor / dealer / commission agent / a
|
||||
DocType: Customer Group,Has Child Node,Имеет дочерний узел
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +296,{0} against Purchase Order {1},{0} против Заказа {1}
|
||||
DocType: SMS Settings,"Enter static url parameters here (Eg. sender=ERPNext, username=ERPNext, password=1234 etc.)","Введите статические параметры адрес здесь (Например отправитель = ERPNext, имя пользователя = ERPNext, пароль = 1234 и т.д.)"
|
||||
apps/erpnext/erpnext/accounts/utils.py +39,{0} {1} not in any active Fiscal Year. For more details check {2}.,"{0} {1}, не в какой-либо активной финансовый год. Для более подробной информации проверить {2}."
|
||||
apps/erpnext/erpnext/accounts/utils.py +39,{0} {1} not in any active Fiscal Year. For more details check {2}.,"{0} {1}, ни в каком-либо активном финансовом году. Для более подробной информации проверить {2}."
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/default_website.py +26,This is an example website auto-generated from ERPNext,Это пример сайт автоматически сгенерированный из ERPNext
|
||||
apps/erpnext/erpnext/accounts/report/accounts_payable/accounts_payable.js +37,Ageing Range 1,Старение Диапазон 1
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +109,Photochemical machining,Фотохимические обработки
|
||||
@ -2505,7 +2505,7 @@ DocType: Payment Tool,Against Vouchers,На ваучеры
|
||||
apps/erpnext/erpnext/accounts/page/accounts_browser/accounts_browser.js +23,Quick Help,Быстрая помощь
|
||||
apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py +169,Source and target warehouse cannot be same for row {0},Источник и цель склад не может быть одинаковым для ряда {0}
|
||||
DocType: Features Setup,Sales Extras,Продажи Дополнительно
|
||||
apps/erpnext/erpnext/accounts/utils.py +311,{0} budget for Account {1} against Cost Center {2} will exceed by {3},{0} бюджет на счет {1} против МВЗ {2} будет превышать {3}
|
||||
apps/erpnext/erpnext/accounts/utils.py +311,{0} budget for Account {1} against Cost Center {2} will exceed by {3},{0} бюджет на счет {1} к МВЗ {2} будет превышать {3}
|
||||
apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +236,"Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry","Разница аккаунт должен быть тип счета активов / пассивов, так как это со Примирение запись Открытие"
|
||||
apps/erpnext/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +123,Purchase Order number required for Item {0},Число Заказ требуется для Пункт {0}
|
||||
DocType: Leave Allocation,Carry Forwarded Leaves,Carry направляются листья
|
||||
@ -2796,7 +2796,7 @@ DocType: Notification Control,Custom Message,Текст сообщения
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/industry_type.py +32,Investment Banking,Инвестиционно-банковская деятельность
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/setup_wizard.js +261,"Select your Country, Time Zone and Currency","Выберите страну, часовой пояс и валюта"
|
||||
apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +314,Cash or Bank Account is mandatory for making payment entry,Наличными или банковский счет является обязательным для внесения записи платежей
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py +214,{0} {1} status is Unstopped,{0} {1} статус отверзутся
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py +214,{0} {1} status is Unstopped,{0} {1} статус активирован
|
||||
DocType: Purchase Invoice,Price List Exchange Rate,Прайс-лист валютный курс
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +90,Pickling,Маринование
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +17,Sand casting,Литье в песчаные
|
||||
@ -3063,7 +3063,7 @@ DocType: Sales Invoice,Posting Time,Средняя Время
|
||||
DocType: Sales Order,% Amount Billed,% Сумма счета
|
||||
apps/erpnext/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +129,Telephone Expenses,Телефон Расходы
|
||||
DocType: Sales Partner,Logo,Логотип
|
||||
apps/erpnext/erpnext/stock/doctype/serial_no/serial_no.py +212,{0} Serial Numbers required for Item {0}. Only {0} provided.,"{0} Серийные номера, необходимые для Пункт {0}. Только {0} предусмотрено."
|
||||
apps/erpnext/erpnext/stock/doctype/serial_no/serial_no.py +212,{0} Serial Numbers required for Item {0}. Only {0} provided.,"{0} Серийные номера, обязательные для позиции {0}. Только {0} предусмотрено."
|
||||
DocType: Naming Series,Check this if you want to force the user to select a series before saving. There will be no default if you check this.,"Проверьте это, если вы хотите, чтобы заставить пользователя выбрать серию перед сохранением. Там не будет по умолчанию, если вы проверить это."
|
||||
apps/erpnext/erpnext/stock/get_item_details.py +107,No Item with Serial No {0},Нет товара с серийным № {0}
|
||||
apps/erpnext/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +68,Direct Expenses,Прямые расходы
|
||||
@ -3122,7 +3122,7 @@ apps/erpnext/erpnext/config/selling.py +18,Quotes to Leads or Customers.,Кот
|
||||
DocType: Stock Settings,Role Allowed to edit frozen stock,Роль разрешено редактировать Замороженный исходный
|
||||
,Territory Target Variance Item Group-Wise,Территория Целевая Разница Пункт Группа Мудрого
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/install_fixtures.py +101,All Customer Groups,Все Группы клиентов
|
||||
apps/erpnext/erpnext/controllers/accounts_controller.py +399,{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.,"{0} является обязательным. Может быть, Обмен валюты запись не создана для {1} по {2}."
|
||||
apps/erpnext/erpnext/controllers/accounts_controller.py +399,{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.,"{0} является обязательным. Может быть, запись Обмен валюты не создана для {1} по {2}."
|
||||
apps/erpnext/erpnext/accounts/doctype/account/account.py +37,Account {0}: Parent account {1} does not exist,Счет {0}: Родитель счета {1} не существует
|
||||
DocType: Purchase Invoice Item,Price List Rate (Company Currency),Прайс-лист Тариф (Компания Валюта)
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_common/purchase_common.py +83,{0} {1} status is 'Stopped',"{0} {1} статус ""Остановлен"""
|
||||
@ -3147,7 +3147,7 @@ DocType: Lead,Add to calendar on this date,Добавить в календар
|
||||
apps/erpnext/erpnext/config/selling.py +132,Rules for adding shipping costs.,Правила для добавления стоимости доставки.
|
||||
apps/erpnext/erpnext/support/doctype/warranty_claim/warranty_claim.py +20,Customer is required,Требуется клиентов
|
||||
DocType: Letter Head,Letter Head,Заголовок письма
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +21,{0} is mandatory for Return,{0} является обязательным для возвращения
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +21,{0} is mandatory for Return,{0} является обязательным для возврата
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_order/purchase_order_list.js +12,To Receive,Получить
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +32,Shrink fitting,Термоусадочная Место
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/setup_wizard.js +522,user@example.com,user@example.com
|
||||
@ -3232,7 +3232,7 @@ apps/erpnext/erpnext/setup/page/setup_wizard/install_fixtures.py +44,Casual Leav
|
||||
DocType: Batch,Batch ID,ID партии
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +300,Note: {0},Примечание: {0}
|
||||
,Delivery Note Trends,Доставка Примечание тенденции
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_common/purchase_common.py +72,{0} must be a Purchased or Sub-Contracted Item in row {1},{0} должен быть куплены или субподрядчиком Пункт в строке {1}
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_common/purchase_common.py +72,{0} must be a Purchased or Sub-Contracted Item in row {1},{0} позиция должна быть куплена или получена на основе субподряда в строке {1}
|
||||
apps/erpnext/erpnext/accounts/general_ledger.py +92,Account: {0} can only be updated via Stock Transactions,Счет: {0} можно обновить только с помощью биржевых операций
|
||||
DocType: GL Entry,Party,Сторона
|
||||
DocType: Sales Order,Delivery Date,Дата поставки
|
||||
@ -3530,7 +3530,7 @@ apps/erpnext/erpnext/projects/doctype/project/project_list.js +6,{0}% Complete,{
|
||||
DocType: Employee,Educational Qualification,Образовательный ценз
|
||||
DocType: Workstation,Operating Costs,Операционные расходы
|
||||
DocType: Employee Leave Approver,Employee Leave Approver,Сотрудник Оставить утверждающий
|
||||
apps/erpnext/erpnext/crm/doctype/newsletter/newsletter.py +167,{0} has been successfully added to our Newsletter list.,{0} был успешно добавлен в список наших новости.
|
||||
apps/erpnext/erpnext/crm/doctype/newsletter/newsletter.py +167,{0} has been successfully added to our Newsletter list.,{0} был успешно добавлен в список наших новостей.
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py +235,Row {0}: An Reorder entry already exists for this warehouse {1},Ряд {0}: запись Изменить порядок уже существует для этого склада {1}
|
||||
apps/erpnext/erpnext/crm/doctype/opportunity/opportunity.py +66,"Cannot declare as lost, because Quotation has been made.","Не можете объявить как потерял, потому что цитаты было сделано."
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +132,Electron beam machining,Электронно-лучевая обработка
|
||||
@ -3822,7 +3822,7 @@ DocType: Customer,Credit Days Based On,Кредитные дней основа
|
||||
apps/erpnext/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py +57,Stock balances updated,Акции остатки обновляются
|
||||
DocType: Selling Settings,Maintain Same Rate Throughout Sales Cycle,Поддержание же скоростью протяжении цикла продаж
|
||||
DocType: Manufacturing Settings,Plan time logs outside Workstation Working Hours.,Планировать время журналы за пределами рабочего времени рабочих станций.
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_common/purchase_common.py +91,{0} {1} has already been submitted,{0} {1} уже отправлен
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_common/purchase_common.py +91,{0} {1} has already been submitted,{0} {1} уже проведен
|
||||
,Items To Be Requested,"Предметы, будет предложено"
|
||||
DocType: Purchase Order,Get Last Purchase Rate,Получить последнюю покупку Оценить
|
||||
DocType: Company,Company Info,Информация о компании
|
||||
@ -3849,7 +3849,7 @@ apps/erpnext/erpnext/config/accounts.py +18,Bills raised to Customers.,"Зако
|
||||
DocType: DocField,Default,По умолчанию
|
||||
apps/erpnext/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py +26,Project Id,Проект Id
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +431,Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2},"Ряд Нет {0}: Сумма не может быть больше, чем ожидании Сумма против Расход претензии {1}. В ожидании сумма {2}"
|
||||
apps/erpnext/erpnext/crm/doctype/newsletter_list/newsletter_list.py +42,{0} subscribers added,{0} добавил абоненты
|
||||
apps/erpnext/erpnext/crm/doctype/newsletter_list/newsletter_list.py +42,{0} subscribers added,{0} подписчики добавлены
|
||||
DocType: Maintenance Schedule,Schedule,Расписание
|
||||
DocType: Account,Parent Account,Родитель счета
|
||||
DocType: Serial No,Available,имеется
|
||||
|
|
File diff suppressed because it is too large
Load Diff
@ -3334,7 +3334,7 @@ apps/frappe/frappe/core/doctype/user/user.js +134,Loading,Yükleme
|
||||
DocType: BOM Replace Tool,BOM Replace Tool,BOM Aracı değiştirin
|
||||
apps/erpnext/erpnext/config/setup.py +37,Country wise default Address Templates,Ülke bilgisi varsayılan adres şablonları
|
||||
apps/erpnext/erpnext/accounts/party.py +220,Due / Reference Date cannot be after {0},Due / Referans Tarihi sonra olamaz {0}
|
||||
apps/erpnext/erpnext/config/setup.py +51,Data Import and Export,Veri İthalat ve İhracat
|
||||
apps/erpnext/erpnext/config/setup.py +51,Data Import and Export,İçeri/Dışarı Aktar
|
||||
DocType: Features Setup,If you involve in manufacturing activity. Enables Item 'Is Manufactured',Üretim faaliyetlerinde bulunuyorsanız Ürünlerin 'Üretilmiş' olmasını sağlar
|
||||
DocType: Sales Invoice,Rounded Total,Yuvarlanmış Toplam
|
||||
DocType: Product Bundle,List items that form the package.,Ambalajı oluşturan Ürünleri listeleyin
|
||||
|
|
@ -3938,7 +3938,7 @@ DocType: Hub Settings,Seller Name,賣家名稱
|
||||
DocType: Purchase Invoice,Taxes and Charges Deducted (Company Currency),稅收和扣除(公司貨幣)
|
||||
DocType: Item Group,General Settings,一般設定
|
||||
apps/erpnext/erpnext/setup/doctype/currency_exchange/currency_exchange.py +19,From Currency and To Currency cannot be same,原始貨幣和目標貨幣不能相同
|
||||
DocType: Stock Entry,Repack,改裝
|
||||
DocType: Stock Entry,Repack,重新包裝
|
||||
apps/erpnext/erpnext/setup/doctype/email_digest/email_digest.js +6,You must Save the form before proceeding,在繼續之前,您必須儲存表單
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/setup_wizard.js +500,Attach Logo,附加標誌
|
||||
DocType: Customer,Commission Rate,佣金率
|
||||
|
|
@ -17,3 +17,68 @@ def load_address_and_contact(doc, key):
|
||||
doc.get("__onload").contact_list = frappe.get_all("Contact",
|
||||
fields="*", filters={key: doc.name},
|
||||
order_by="is_primary_contact desc, modified desc")
|
||||
|
||||
def has_permission(doc, ptype, user):
|
||||
links = get_permitted_and_not_permitted_links(doc.doctype)
|
||||
if not links.get("not_permitted_links"):
|
||||
# optimization: don't determine permissions based on link fields
|
||||
return True
|
||||
|
||||
# True if any one is True or all are empty
|
||||
names = []
|
||||
for df in (links.get("permitted_links") + links.get("not_permitted_links")):
|
||||
doctype = df.options
|
||||
name = doc.get(df.fieldname)
|
||||
|
||||
names.append(name)
|
||||
|
||||
if name and frappe.has_permission(doctype, ptype, doc=name):
|
||||
return True
|
||||
|
||||
if not any(names):
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_permission_query_conditions_for_contact(user):
|
||||
return get_permission_query_conditions("Contact")
|
||||
|
||||
def get_permission_query_conditions_for_address(user):
|
||||
return get_permission_query_conditions("Address")
|
||||
|
||||
def get_permission_query_conditions(doctype):
|
||||
links = get_permitted_and_not_permitted_links(doctype)
|
||||
|
||||
if not links.get("not_permitted_links"):
|
||||
# when everything is permitted, don't add additional condition
|
||||
return ""
|
||||
|
||||
else:
|
||||
conditions = []
|
||||
|
||||
for df in links.get("permitted_links"):
|
||||
# like ifnull(customer, '')!='' or ifnull(supplier, '')!=''
|
||||
conditions.append("ifnull(`tab{doctype}`.`{fieldname}`, '')!=''".format(doctype=doctype, fieldname=df.fieldname))
|
||||
|
||||
return "( " + " or ".join(conditions) + " )"
|
||||
|
||||
def get_permitted_and_not_permitted_links(doctype):
|
||||
permitted_links = []
|
||||
not_permitted_links = []
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
for df in meta.get_link_fields():
|
||||
if df.options not in ("Customer", "Supplier", "Sales Partner"):
|
||||
continue
|
||||
|
||||
if frappe.has_permission(df.options):
|
||||
permitted_links.append(df)
|
||||
else:
|
||||
not_permitted_links.append(df)
|
||||
|
||||
return {
|
||||
"permitted_links": permitted_links,
|
||||
"not_permitted_links": not_permitted_links
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user