Compare commits

..

103 Commits

Author SHA1 Message Date
Nabin Hait
72cd206286 Merge branch 'develop' 2015-10-07 17:27:23 +05:30
Nabin Hait
9801745090 bumped to version 6.4.6 2015-10-07 17:57:23 +06:00
Anand Doshi
7866c6e6db [fix] modified in sales order and purchase order 2015-10-07 17:26:19 +05:30
Nabin Hait
5812fdb574 Merge branch 'develop' 2015-10-07 14:40:32 +05:30
Nabin Hait
4c502bcd26 bumped to version 6.4.5 2015-10-07 15:10:32 +06:00
Nabin Hait
714948c867 Merge pull request #4136 from nabinhait/uom
[fix] Fetch UOM Conversion Factor from template if not specified in variants
2015-10-07 14:30:18 +05:30
Nabin Hait
8f3b360f83 Merge pull request #4133 from saurabh6790/erp_support
Calendar view for holiday listing
2015-10-07 14:29:38 +05:30
Nabin Hait
314086d6c0 [fix] Fetch UOM Conversion Factor from template if not specified in variants 2015-10-07 11:55:08 +05:30
Nabin Hait
bcd655a985 Merge pull request #4109 from rmehta/fix-sales-purchase-order-status
[fix] sales & purchase order status
2015-10-07 11:34:30 +05:30
Saurabh
b0dbdc1439 minor fixes 2015-10-07 11:27:25 +05:30
Nabin Hait
37b0bf257d Merge pull request #4117 from nabinhait/sample_data
[fix] Create sample opportunity and quotation only if Customer exists
2015-10-07 11:25:50 +05:30
Nabin Hait
c5a25f44e1 Merge pull request #4119 from nabinhait/fix3
[fix] Return account currency only if account provided
2015-10-07 11:25:19 +05:30
Nabin Hait
8dafa376ab Merge pull request #4123 from nabinhait/fix4
[fix] allowed more than 2 currencies in Journal Entry
2015-10-07 11:24:45 +05:30
Saurabh
74eb8e34da [Fixes] minor fix for fiscal year in holiday list 2015-10-07 11:17:14 +05:30
Nabin Hait
5c66fb7631 Merge pull request #4130 from nabinhait/reports
[fix][report] Left join used in pending reports
2015-10-07 11:15:36 +05:30
Nabin Hait
be3b3b2107 Merge pull request #4132 from nabinhait/credit_limit
minor fix in credit limit
2015-10-07 11:15:02 +05:30
Nabin Hait
9ab09fd1d6 Merge pull request #4134 from nabinhait/valuation_rate
[fix] Valuation Rate in Stock Balance report
2015-10-07 11:09:56 +05:30
Nabin Hait
8a7bdd5a92 [fix] Valuation Rate in Stock Balance report 2015-10-06 18:46:56 +05:30
Saurabh
1d753c92b1 Calendar view for holiday listing 2015-10-06 15:21:23 +05:30
Nabin Hait
54ecc8ebba minor fix in credit limit 2015-10-06 14:28:41 +05:30
Nabin Hait
f4edaef481 [fix][report] Left join used in pending reports 2015-10-06 12:48:04 +05:30
Nabin Hait
ebbd163903 [fix] allowed more than 2 currencies in Journal Entry 2015-10-05 13:21:38 +05:30
Nabin Hait
8954b24b22 [fix] Return account currency only if account provided 2015-10-04 12:27:46 +05:30
Nabin Hait
ae92fc7f35 [fix] Create sample opportunity and quotation only if Customer exists 2015-10-03 13:42:44 +05:30
Rushabh Mehta
0bc3ca02f3 [fix] default contact for website 2015-10-03 12:08:43 +05:30
Rushabh Mehta
d10ba853e6 [fix] sales & purchase order status 2015-10-02 17:16:51 +05:30
Anand Doshi
5bcf8315de Merge branch 'develop' 2015-10-02 16:35:36 +05:30
Anand Doshi
789a798e36 bumped to version 6.4.4 2015-10-02 17:05:36 +06:00
Nabin Hait
ee1169dac7 Merge pull request #4112 from nabinhait/fix1
Minor fix
2015-10-02 16:27:49 +05:30
Nabin Hait
fbef1fdf3a Minor fix 2015-10-02 16:26:18 +05:30
Anand Doshi
4358e1cd46 Merge pull request #4111 from nabinhait/payment_reco_patch
[patch] Fix affected Journal Entries due to reconciliation
2015-10-02 16:05:03 +05:30
Nabin Hait
7a287a9153 [patch] Fix affected Journal Entries due to reconciliation 2015-10-02 16:00:42 +05:30
Anand Doshi
5a49ded5d9 Merge branch 'develop' 2015-10-02 12:43:27 +05:30
Anand Doshi
71f23acc2b bumped to version 6.4.3 2015-10-02 13:13:27 +06:00
Anand Doshi
1a1f790150 [fix] test case 2015-10-02 12:34:18 +05:30
Rushabh Mehta
3c54e9779b Merge pull request #4108 from nabinhait/payment_reco
[fix] Payment Reconciliation in multi-currency
2015-10-02 12:32:54 +05:30
Nabin Hait
db48b7d764 [fix] Payment Reconciliation in multi-currency 2015-10-02 12:05:55 +05:30
Rushabh Mehta
83c0899c83 [fix] [minor] default ranges for demo 2015-10-02 11:31:37 +05:30
Nabin Hait
e5047ec90a Merge pull request #4106 from anandpdoshi/payment-tool-amount
In Payment Tool, Set Payment Amount = Outstanding Amount if checked
2015-10-02 10:39:57 +05:30
Anand Doshi
d9ab725be4 [fix] In Payment Tool, Set Payment Amount = Outstanding Amount if checked 2015-10-01 19:17:02 +05:30
Anand Doshi
7afaeb0820 [fix] the case when party account currency is missing 2015-10-01 18:55:43 +05:30
Anand Doshi
da2d8b958d Merge branch 'develop' 2015-10-01 14:01:22 +05:30
Anand Doshi
ba48f82e03 bumped to version 6.4.2 2015-10-01 14:31:22 +06:00
Anand Doshi
8e7e128e81 Merge pull request #4104 from nabinhait/addresses_and_contacts
[fix] get permission query condition if no permitted links
2015-10-01 14:00:38 +05:30
Anand Doshi
0353569e8b Merge pull request #4103 from nabinhait/monthly_attendance_sheet
[fix] Month issue fixed in Monthly Attendance Sheet
2015-10-01 13:58:33 +05:30
Anand Doshi
665e2f5418 Merge pull request #4100 from nabinhait/general_ledger_report
[fix] General Ledger report fixed
2015-10-01 13:56:13 +05:30
Nabin Hait
dace2b6796 [fix] get permission query condition if no permitted links 2015-10-01 13:54:46 +05:30
Nabin Hait
712b02593a [fix] Month issue fixed in Monthly Attendance Sheet 2015-10-01 12:35:54 +05:30
Anand Doshi
d2a60fd727 Merge pull request #4102 from nabinhait/lead_email
[fix] Validate duplicate email ids in Lead
2015-10-01 12:07:37 +05:30
Nabin Hait
f924e08b93 [fix] Validate duplicate email ids in Lead 2015-10-01 11:51:48 +05:30
Nabin Hait
5c623dae4d [fix] General Ledger report fixed 2015-10-01 11:22:15 +05:30
Anand Doshi
e2c3d40b57 [hotfix] Payment Reconciliation Invoice Type 2015-10-01 11:21:26 +05:30
Anand Doshi
673887455f [fix] Show Contribution (%) in Sales Team table in Customer Form 2015-10-01 11:21:25 +05:30
Anand Doshi
63199e486b Merge branch 'develop' 2015-09-30 20:51:00 +05:30
Anand Doshi
bf7294cf5c bumped to version 6.4.1 2015-09-30 21:20:59 +06:00
Anand Doshi
8aa06a809a Merge pull request #4096 from nabinhait/jv_reference
Repost GL Entries for Journal Entries where reference name is missing
2015-09-30 19:44:21 +05:30
Nabin Hait
72d2d682ae Repost GL Entries for Journal Entries where reference name is missing 2015-09-30 18:50:54 +05:30
Anand Doshi
21f6ea6f7e [hotfix] remove allow on submit from item tables 2015-09-30 18:45:29 +05:30
Anand Doshi
185f488c51 Merge branch 'develop' 2015-09-30 17:23:52 +05:30
Anand Doshi
f3006972d5 bumped to version 6.4.0 2015-09-30 17:53:52 +06:00
Anand Doshi
dccc6bc11d [change-log] 2015-09-30 17:19:51 +05:30
Anand Doshi
15d2c89939 Merge remote-tracking branch 'upstream/master' into develop
Conflicts:
	erpnext/accounts/report/general_ledger/general_ledger.py
2015-09-30 17:13:58 +05:30
Anand Doshi
ca4c8a2a46 [translations] 2015-09-30 17:10:58 +05:30
Anand Doshi
c320fe541d Merge pull request #4067 from anandpdoshi/remove-party-account-currency
Remove party_account_currency from Customer and Supplier, and use currency derived from get_party_account
2015-09-30 16:55:24 +05:30
Anand Doshi
f40a87511e Merge pull request #4091 from rmehta/repack-allow-low-rate
[fix] remove validation to disallow lower rate
2015-09-30 16:46:31 +05:30
Anand Doshi
4945b94950 [fix] added validation to match account currency with existing gle and test case 2015-09-30 16:41:15 +05:30
Rushabh Mehta
cb96b61449 [fix] remove validation to disallow lower rate 2015-09-30 16:22:05 +05:30
Anand Doshi
248c867a2c [minor] removed test case for party multi-currency validation, since party multi-currency is now based on account 2015-09-30 15:11:13 +05:30
Anand Doshi
da98ab6f3c [fix] Only 1 account per company for a party 2015-09-30 15:11:12 +05:30
Anand Doshi
cd0989e051 Added method get_account_currency 2015-09-30 15:11:12 +05:30
Anand Doshi
b20baf894f [fix] Remove party_account_currency from Customer and Supplier, and use currency derived from get_party_account 2015-09-30 15:11:12 +05:30
Nabin Hait
d3cf4f1264 Merge pull request #4075 from anandpdoshi/journal-entry-get-exchange-rate
return exchange rate as 1 if no account is specified
2015-09-30 15:05:41 +05:30
Anand Doshi
2dbe2b63b2 Merge pull request #4088 from anandpdoshi/address-contact-permissions
Apply permissions on address and contact based on permissions of Customer, Supplier
2015-09-30 12:19:50 +05:30
Anand Doshi
edba8f5582 [fix] Print 'Attach' field in Stock Entry Detail 2015-09-30 11:50:08 +05:30
Anand Doshi
89b8d11f9c [fix] Apply permissions on address and contact based on permissions of Customer, Supplier 2015-09-29 20:11:57 +05:30
Anand Doshi
19a33994da [cleanup] removed desk_home and desk_home_flows from conf.js 2015-09-29 20:06:22 +05:30
Rushabh Mehta
89349d3ae3 [fix] [payment-reconciliation] ignore rows with blank invoice numbers 2015-09-29 16:39:53 +05:30
Anand Doshi
f6a31a568a Merge pull request #4085 from rmehta/bulk-priority
[enhancement] lower priority for newsletter emails
2015-09-29 15:25:13 +05:30
Rushabh Mehta
4bcbcd29f7 [enhancement] lower priority for newsletter emails 2015-09-29 14:58:45 +05:30
Anand Doshi
740a11263f [minor] validate invoice number is selected in Payment Reconciliation 2015-09-29 12:55:11 +05:30
Anand Doshi
142859f36e Merge pull request #4076 from anandpdoshi/shopping-cart-order
check doc.has_website_permission in order.html. Merge after frappe/frappe#1316
2015-09-28 19:40:17 +05:30
Anand Doshi
6534ad082d Merge pull request #4069 from anandpdoshi/round-status-updater-percentages
Round status update percentages to 2 decimals
2015-09-28 19:40:01 +05:30
Anand Doshi
6361ae3495 Merge branch 'rmehta-backup-cleanup' into develop 2015-09-28 19:38:06 +05:30
Rushabh Mehta
7f75dbf061 [cleanup] remove backup manager 2015-09-28 19:12:54 +05:30
Anand Doshi
199d8a44fc [fix] check doc.has_website_permission in order.html 2015-09-28 19:04:42 +05:30
Rushabh Mehta
72b1128467 [fixes] accounts controller add flt 2015-09-28 17:29:46 +05:30
Anand Doshi
d6cb55ad1a [fix] return exchange rate as 1 if no account is specified 2015-09-28 17:20:02 +05:30
Anand Doshi
c76e34d7de [fix] In get_bom_items_as_dict don't filter by is_pro_applicable for multi-level bom 2015-09-28 17:01:44 +05:30
Anand Doshi
33b10faf94 Merge pull request #4074 from anandpdoshi/multi-level-bom-fetch
[fix] In get_bom_items_as_dict don't filter by is_pro_applicable for multi-level bom
2015-09-28 17:00:52 +05:30
Anand Doshi
d970b001a4 [fix] Round status update percentages to 2 decimals 2015-09-28 16:52:22 +05:30
Anand Doshi
d767fb6134 [fix] In get_bom_items_as_dict don't filter by is_pro_applicable for multi-level bom 2015-09-28 16:44:48 +05:30
Rushabh Mehta
6239923340 Merge pull request #4071 from gmarke/patch-2
Update get_item_details.py
2015-09-28 10:18:56 +05:30
gmarke
e5a31462fe Update get_item_details.py
like this it will be possible to correctly handle cost center for stock entries when no default cost center per company is set
2015-09-27 09:38:01 +02:00
Anand Doshi
6f39300d43 Merge pull request #4068 from anandpdoshi/due-date-validation
if no party, don't validate due date based on credit days
2015-09-25 17:31:33 +05:30
Anand Doshi
0ca587e018 [fix] if no party, don't validate due date based on credit days 2015-09-25 16:32:14 +05:30
Rushabh Mehta
8ffe12ebe4 [chart-of-accounts] added default for Guatemala 2015-09-25 15:40:33 +05:30
Anand Doshi
8579af371c Merge pull request #4057 from rmehta/gl-report-fix
GL Report Fix
2015-09-25 12:30:26 +05:30
Rushabh Mehta
b5ff9421e1 [optimize] customer outstanding query 2015-09-25 12:25:12 +05:30
Rushabh Mehta
ba8ec17f0b Merge pull request #4065 from nabinhait/receivable_payable
[fix] Fetch gl entries in receivable/payable report only if party mentioned
2015-09-25 11:34:14 +05:30
Nabin Hait
44bd3b2601 [fix] Fetch gl entries in receivable/payable report only if party mentioned 2015-09-25 10:33:51 +05:30
Rushabh Mehta
9d40eca428 Merge pull request #4064 from nabinhait/trial_balance_for_party
[report] Trial Balance for Party
2015-09-25 10:01:44 +05:30
Nabin Hait
1b6c00e2c7 [report] Trial Balance for Party 2015-09-25 09:18:03 +05:30
Rushabh Mehta
8f2b8afcb7 [fix] gl entry report if not filtered 2015-09-24 15:03:53 +05:30
107 changed files with 10479 additions and 10864 deletions

View File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = '6.3.2'
__version__ = '6.4.6'

View File

@@ -50,20 +50,20 @@ class Account(Document):
def set_root_and_report_type(self):
if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
if par.report_type:
self.report_type = par.report_type
if par.root_type:
self.root_type = par.root_type
if self.is_group:
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value:
if self.report_type != db_value.report_type:
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
(self.report_type, self.lft, self.rgt))
if self.root_type != db_value.root_type:
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
(self.root_type, self.lft, self.rgt))
def validate_root_details(self):
@@ -89,11 +89,11 @@ class Account(Document):
frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
elif account_balance < 0 and self.balance_must_be == "Debit":
frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
def validate_account_currency(self):
if not self.account_currency:
self.account_currency = frappe.db.get_value("Company", self.company, "default_currency")
elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
@@ -207,3 +207,16 @@ def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
and %s like %s order by name limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
def get_account_currency(account):
"""Helper function to get account currency"""
if not account:
return
def generator():
account_currency, company = frappe.db.get_value("Account", account, ["account_currency", "company"])
if not account_currency:
account_currency = frappe.db.get_value("Company", company, "default_currency")
return account_currency
return frappe.local_cache("account_currency", account, generator)

View File

@@ -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"
}
}
}

View File

@@ -1,32 +1,38 @@
[
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2012",
"year_end_date": "2012-12-31",
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2012",
"year_end_date": "2012-12-31",
"year_start_date": "2012-01-01"
},
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2013",
"year_end_date": "2013-12-31",
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2013",
"year_end_date": "2013-12-31",
"year_start_date": "2013-01-01"
},
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2014",
"year_end_date": "2014-12-31",
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2014",
"year_end_date": "2014-12-31",
"year_start_date": "2014-01-01"
},
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2015",
"year_end_date": "2015-12-31",
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2015",
"year_end_date": "2015-12-31",
"year_start_date": "2015-01-01"
},
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2016",
"year_end_date": "2016-12-31",
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2016",
"year_end_date": "2016-12-31",
"year_start_date": "2016-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2017",
"year_end_date": "2017-12-31",
"year_start_date": "2017-01-01"
}
]
]

View File

@@ -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":

View File

@@ -335,6 +335,7 @@ frappe.ui.form.on("Journal Entry Account", {
party: function(frm, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
if(!d.account && d.party_type && d.party) {
if(!frm.doc.company) frappe.throw(__("Please select Company"));
return frm.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
child: d,

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ import frappe
from frappe.utils import cstr, flt, fmt_money, formatdate
from frappe import msgprint, _, scrub
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.utils import get_balance_on
from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.setup.utils import get_company_currency
@@ -146,7 +146,7 @@ class JournalEntry(AccountsController):
self.reference_totals = {}
self.reference_types = {}
self.reference_parties = {}
self.reference_accounts = {}
for d in self.get("accounts"):
if not d.reference_type:
@@ -169,8 +169,7 @@ class JournalEntry(AccountsController):
self.reference_totals[d.reference_name] = 0.0
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
self.reference_types[d.reference_name] = d.reference_type
if d.party_type and d.party:
self.reference_parties[d.reference_name] = [d.party_type, d.party]
self.reference_accounts[d.reference_name] = d.account
against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
[scrub(dt) for dt in field_dict.get(d.reference_type)])
@@ -196,7 +195,7 @@ class JournalEntry(AccountsController):
"""Validate totals, stopped and docstatus for orders"""
for reference_name, total in self.reference_totals.iteritems():
reference_type = self.reference_types[reference_name]
party_type, party = self.reference_parties.get(reference_name)
account = self.reference_accounts[reference_name]
if reference_type in ("Sales Order", "Purchase Order"):
order = frappe.db.get_value(reference_type, reference_name,
@@ -212,8 +211,8 @@ class JournalEntry(AccountsController):
if cstr(order.status) == "Stopped":
frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name))
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency")
if party_account_currency == self.company_currency:
account_currency = get_account_currency(account)
if account_currency == self.company_currency:
voucher_total = order.base_grand_total
else:
voucher_total = order.grand_total
@@ -278,9 +277,6 @@ class JournalEntry(AccountsController):
if not self.multi_currency:
frappe.throw(_("Please check Multi Currency option to allow accounts with other currency"))
if len(alternate_currency) > 1:
frappe.throw(_("Only one alternate currency can be used in a single Journal Entry"))
self.set_exchange_rate()
for d in self.get("accounts"):
@@ -609,8 +605,8 @@ def get_payment_entry_from_sales_order(sales_order):
jv = get_payment_entry(so)
jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name)
party_account = get_party_account(so.company, so.customer, "Customer")
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
party_account = get_party_account("Customer", so.customer, so.company)
party_account_currency = get_account_currency(party_account)
exchange_rate = get_exchange_rate(party_account, party_account_currency, so.company)
@@ -660,8 +656,8 @@ def get_payment_entry_from_purchase_order(purchase_order):
jv = get_payment_entry(po)
jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name)
party_account = get_party_account(po.company, po.supplier, "Supplier")
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
party_account = get_party_account("Supplier", po.supplier, po.company)
party_account_currency = get_account_currency(party_account)
exchange_rate = get_exchange_rate(party_account, party_account_currency, po.company)
@@ -779,7 +775,7 @@ def get_party_account_and_balance(company, party_type, party):
frappe.msgprint(_("No Permission"), raise_exception=1)
from erpnext.accounts.party import get_party_account
account = get_party_account(company, party, party_type)
account = get_party_account(party_type, party, company)
account_balance = get_balance_on(account=account)
party_balance = get_balance_on(party_type=party_type, party=party)
@@ -826,17 +822,20 @@ def get_exchange_rate(account, account_currency, company,
if account_currency != company_currency:
if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name:
exchange_rate = frappe.db.get_value(reference_type, reference_name, "conversion_rate")
elif account_details.account_type == "Bank" and \
elif account_details and account_details.account_type == "Bank" and \
((account_details.root_type == "Asset" and flt(credit) > 0) or
(account_details.root_type == "Liability" and debit)):
exchange_rate = get_average_exchange_rate(account)
if not exchange_rate:
if not exchange_rate and account_currency:
exchange_rate = get_exchange_rate(account_currency, company_currency)
else:
exchange_rate = 1
return exchange_rate
# don't return None or 0 as it is multipled with a value and that value could be lost
return exchange_rate or 1
def get_average_exchange_rate(account):
exchange_rate = 0

View File

@@ -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:

View File

@@ -74,21 +74,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
doc: me.frm.doc,
method: 'get_unreconciled_entries',
callback: function(r, rt) {
var invoices = [];
$.each(me.frm.doc.invoices || [], function(i, row) {
if (row.invoice_number && !inList(invoices, row.invoice_number))
invoices.push(row.invoice_type + " | " + row.invoice_number);
});
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
me.frm.doc.name).options = invoices.join("\n");
$.each(me.frm.doc.payments || [], function(i, p) {
if(!inList(invoices, cstr(p.invoice_number))) p.invoice_number = null;
});
refresh_field("payments");
me.set_invoice_options();
}
});
@@ -98,8 +84,29 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
var me = this;
return this.frm.call({
doc: me.frm.doc,
method: 'reconcile'
method: 'reconcile',
callback: function(r, rt) {
me.set_invoice_options();
}
});
},
set_invoice_options: function() {
var invoices = [];
$.each(me.frm.doc.invoices || [], function(i, row) {
if (row.invoice_number && !inList(invoices, row.invoice_number))
invoices.push(row.invoice_type + " | " + row.invoice_number);
});
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
me.frm.doc.name).options = invoices.join("\n");
$.each(me.frm.doc.payments || [], function(i, p) {
if(!inList(invoices, cstr(p.invoice_number))) p.invoice_number = null;
});
refresh_field("payments");
}
});

View File

@@ -3,11 +3,8 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
from frappe import msgprint, _
from frappe.model.document import Document
class PaymentReconciliation(Document):
@@ -17,7 +14,8 @@ class PaymentReconciliation(Document):
def get_jv_entries(self):
self.check_mandatory_to_fetch()
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
dr_or_cr = "credit_in_account_currency" if self.party_type == "Customer" \
else "debit_in_account_currency"
cond = self.check_condition(dr_or_cr)
@@ -68,9 +66,9 @@ class PaymentReconciliation(Document):
def get_invoice_entries(self):
#Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
non_reconciled_invoices = []
dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
dr_or_cr = "debit_in_account_currency" if self.party_type == "Customer" else "credit_in_account_currency"
cond = self.check_condition(dr_or_cr)
invoice_list = frappe.db.sql("""
select
voucher_no, voucher_type, posting_date,
@@ -106,13 +104,15 @@ class PaymentReconciliation(Document):
and account = %(account)s and {0} > 0
and against_voucher_type = %(against_voucher_type)s
and ifnull(against_voucher, '') = %(against_voucher)s
""".format("credit" if self.party_type == "Customer" else "debit"), {
"party_type": self.party_type,
"party": self.party,
"account": self.receivable_payable_account,
"against_voucher_type": d.voucher_type,
"against_voucher": d.voucher_no
})
""".format("credit_in_account_currency" if self.party_type == "Customer"
else "debit_in_account_currency"), {
"party_type": self.party_type,
"party": self.party,
"account": self.receivable_payable_account,
"against_voucher_type": d.voucher_type,
"against_voucher": d.voucher_no
}
)
payment_amount = payment_amount[0][0] if payment_amount else 0
@@ -141,12 +141,14 @@ class PaymentReconciliation(Document):
def reconcile(self, args):
for e in self.get('payments'):
if " | " in e.invoice_number:
e.invoice_type = None
if e.invoice_number and " | " in e.invoice_number:
e.invoice_type, e.invoice_number = e.invoice_number.split(" | ")
self.get_invoice_entries()
self.validate_invoice()
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
dr_or_cr = "credit_in_account_currency" if self.party_type == "Customer" \
else "debit_in_account_currency"
lst = []
for e in self.get('payments'):
if e.invoice_number and e.allocated_amount:

View File

@@ -141,7 +141,10 @@ frappe.ui.form.on("Payment Tool", "get_outstanding_vouchers", function(frm) {
c.against_voucher_no = d.voucher_no;
c.total_amount = d.invoice_amount;
c.outstanding_amount = d.outstanding_amount;
c.payment_amount = d.outstanding_amount;
if (frm.doc.set_payment_amount) {
c.payment_amount = d.outstanding_amount;
}
});
}
refresh_field("vouchers");

View File

@@ -185,6 +185,28 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "set_payment_amount",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Set Payment Amount = Outstanding Amount",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -474,7 +496,7 @@
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"modified": "2015-08-31 18:58:21.813054",
"modified": "2015-10-01 09:43:24.199025",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Tool",

View File

@@ -7,6 +7,7 @@ from frappe import _, scrub
from frappe.utils import flt
from frappe.model.document import Document
import json
from erpnext.accounts.utils import get_account_currency
class PaymentTool(Document):
def make_journal_entry(self):
@@ -59,7 +60,7 @@ def get_outstanding_vouchers(args):
args = json.loads(args)
party_account_currency = frappe.db.get_value("Account", args.get("party_account"), "account_currency")
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
if args.get("party_type") == "Customer" and args.get("received_or_paid") == "Received":
@@ -112,7 +113,7 @@ def get_orders_to_be_billed(party_type, party, party_account_currency, company_c
@frappe.whitelist()
def get_against_voucher_amount(against_voucher_type, against_voucher_no, party_account, company):
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
party_account_currency = get_account_currency(party_account)
company_currency = frappe.db.get_value("Company", company, "default_currency")
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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

View File

@@ -671,7 +671,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -1002,7 +1002,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "taxes",
@@ -2951,7 +2951,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-09-23 09:52:09.675668",
"modified": "2015-09-30 08:52:53.679566",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -11,6 +11,7 @@ from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController
from erpnext.accounts.utils import get_account_currency
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -186,7 +187,7 @@ class SalesInvoice(SellingController):
pos = self.set_pos_fields(for_validate)
if not self.debit_to:
self.debit_to = get_party_account(self.company, self.customer, "Customer")
self.debit_to = get_party_account("Customer", self.customer, self.company)
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
@@ -206,8 +207,8 @@ class SalesInvoice(SellingController):
def validate_time_logs_are_submitted(self):
for d in self.get("items"):
if d.time_log_batch:
status = frappe.db.get_value("Time Log Batch", d.time_log_batch, "status")
if status!="Submitted":
docstatus = frappe.db.get_value("Time Log Batch", d.time_log_batch, "docstatus")
if docstatus!=1:
frappe.throw(_("Time Log Batch {0} must be 'Submitted'").format(d.time_log_batch))
def set_pos_fields(self, for_validate=False):
@@ -531,7 +532,7 @@ class SalesInvoice(SellingController):
def make_tax_gl_entries(self, gl_entries):
for tax in self.get("taxes"):
if flt(tax.base_tax_amount_after_discount_amount):
account_currency = frappe.db.get_value("Account", tax.account_head, "account_currency")
account_currency = get_account_currency(tax.account_head)
gl_entries.append(
self.get_gl_dict({
"account": tax.account_head,
@@ -547,7 +548,7 @@ class SalesInvoice(SellingController):
# income account gl entries
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = frappe.db.get_value("Account", item.income_account, "account_currency")
account_currency = get_account_currency(item.income_account)
gl_entries.append(
self.get_gl_dict({
"account": item.income_account,
@@ -566,7 +567,7 @@ class SalesInvoice(SellingController):
def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:
bank_account_currency = frappe.db.get_value("Account", self.cash_bank_account, "account_currency")
bank_account_currency = get_account_currency(self.cash_bank_account)
# POS, make payment entries
gl_entries.append(
self.get_gl_dict({
@@ -594,7 +595,7 @@ class SalesInvoice(SellingController):
def make_write_off_gl_entry(self, gl_entries):
# write off entries, applicable if only pos
if self.write_off_account and self.write_off_amount:
write_off_account_currency = frappe.db.get_value("Account", self.write_off_account, "account_currency")
write_off_account_currency = get_account_currency(self.write_off_account)
gl_entries.append(
self.get_gl_dict({

View File

@@ -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):

View File

@@ -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:

View File

@@ -40,6 +40,13 @@ class ReceivablePayableReport(object):
columns += [_("Age (Days)") + "::80"]
if not "range1" in self.filters:
self.filters["range1"] = "30"
if not "range2" in self.filters:
self.filters["range2"] = "60"
if not "range3" in self.filters:
self.filters["range3"] = "90"
for label in ("0-{range1}".format(**self.filters),
"{range1}-{range2}".format(**self.filters),
"{range2}-{range3}".format(**self.filters),
@@ -75,9 +82,9 @@ class ReceivablePayableReport(object):
voucher_details = self.get_voucher_details(args.get("party_type"))
future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type"))
company_currency = frappe.db.get_value("Company", self.filters.get("company"), "default_currency")
data = []
for gle in self.get_entries_till(self.filters.report_date, args.get("party_type")):
if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers):
@@ -117,7 +124,7 @@ class ReceivablePayableReport(object):
row += [self.get_territory(gle.party)]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_type(gle.party)]
if self.filters.get(scrub(args.get("party_type"))):
row.append(gle.account_currency)
else:
@@ -209,7 +216,8 @@ class ReceivablePayableReport(object):
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
voucher_type, voucher_no, against_voucher_type, against_voucher, account_currency, remarks, {0}
from `tabGL Entry`
where docstatus < 2 and party_type=%s {1} order by posting_date, party"""
where docstatus < 2 and party_type=%s and ifnull(party, '') != '' {1}
order by posting_date, party"""
.format(select_fields, conditions), values, as_dict=True)
return self.gl_entries

View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe.utils import flt, getdate, cstr
from frappe import _
from erpnext.accounts.utils import get_account_currency
def execute(filters=None):
account_details = {}
@@ -12,10 +13,10 @@ def execute(filters=None):
account_details.setdefault(acc.name, acc)
validate_filters(filters, account_details)
validate_party(filters)
filters = set_account_currency(filters)
filters = set_account_currency(filters)
columns = get_columns(filters)
@@ -46,49 +47,49 @@ def validate_party(filters):
frappe.throw(_("To filter based on Party, select Party Type first"))
elif not frappe.db.exists(party_type, party):
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
def set_account_currency(filters):
if not (filters.get("account") or filters.get("party")):
return filters
else:
filters["company_currency"] = frappe.db.get_value("Company", filters.company, "default_currency")
account_currency = None
if filters.get("account"):
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
account_currency = get_account_currency(filters.account)
elif filters.get("party"):
gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type,
gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type,
"party": filters.party, "company": filters.company}, "account_currency")
if gle_currency:
account_currency = gle_currency
else:
account_currency = frappe.db.get_value(filters.party_type, filters.party, "default_currency")
filters["account_currency"] = account_currency or filters.company_currency
if filters.account_currency != filters.company_currency:
filters["show_in_account_currency"] = 1
return filters
def get_columns(filters):
columns = [
_("Posting Date") + ":Date:90", _("Account") + ":Link/Account:200",
_("Debit") + ":Float:100", _("Credit") + ":Float:100"
]
if filters.get("show_in_account_currency"):
columns += [
_("Debit") + " (" + filters.account_currency + ")" + ":Float:100",
_("Debit") + " (" + filters.account_currency + ")" + ":Float:100",
_("Credit") + " (" + filters.account_currency + ")" + ":Float:100"
]
columns += [
_("Voucher Type") + "::120", _("Voucher No") + ":Dynamic Link/Voucher Type:160",
_("Against Account") + "::120", _("Party Type") + "::80", _("Party") + "::150",
_("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"
]
return columns
def get_result(filters, account_details):
@@ -101,21 +102,21 @@ def get_result(filters, account_details):
return result
def get_gl_entries(filters):
select_fields = """, sum(ifnull(debit_in_account_currency, 0)) as debit_in_account_currency,
select_fields = """, sum(ifnull(debit_in_account_currency, 0)) as debit_in_account_currency,
sum(ifnull(credit_in_account_currency, 0)) as credit_in_account_currency""" \
if filters.get("show_in_account_currency") else ""
group_by_condition = "group by voucher_type, voucher_no, account, cost_center" \
if filters.get("group_by_voucher") else "group by name"
gl_entries = frappe.db.sql("""select posting_date, account, party_type, party,
sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit,
sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit,
voucher_type, voucher_no, cost_center, remarks, against, is_opening {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{group_by_condition}
order by posting_date, account"""\
.format(select_fields=select_fields, conditions=get_conditions(filters),
.format(select_fields=select_fields, conditions=get_conditions(filters),
group_by_condition=group_by_condition), filters, as_dict=1)
return gl_entries
@@ -135,7 +136,7 @@ def get_conditions(filters):
if filters.get("party"):
conditions.append("party=%(party)s")
if not (filters.get("account") or filters.get("party") or filters.get("group_by_account")):
conditions.append("posting_date >=%(from_date)s")
@@ -156,33 +157,38 @@ def get_data_with_opening_closing(filters, account_details, gl_entries):
if filters.get("account") or filters.get("party"):
data += [get_balance_row(_("Opening"), opening, opening_in_account_currency), {}]
for acc, acc_dict in gle_map.items():
if acc_dict.entries:
# Opening for individual ledger, if grouped by account
if filters.get("group_by_account"):
data.append(get_balance_row(_("Opening"), acc_dict.opening,
if filters.get("group_by_account"):
for acc, acc_dict in gle_map.items():
if acc_dict.entries:
# Opening for individual ledger, if grouped by account
data.append(get_balance_row(_("Opening"), acc_dict.opening,
acc_dict.opening_in_account_currency))
data += acc_dict.entries
data += acc_dict.entries
# Totals and closing for individual ledger, if grouped by account
if filters.get("group_by_account"):
# Totals and closing for individual ledger, if grouped by account
account_closing = acc_dict.opening + acc_dict.total_debit - acc_dict.total_credit
account_closing_in_account_currency = acc_dict.opening_in_account_currency \
+ acc_dict.total_debit_in_account_currency - acc_dict.total_credit_in_account_currency
data += [{"account": "'" + _("Totals") + "'", "debit": acc_dict.total_debit,
"credit": acc_dict.total_credit},
get_balance_row(_("Closing (Opening + Totals)"),
account_closing, account_closing_in_account_currency), {}]
else:
for gl in gl_entries:
if gl.posting_date >= getdate(filters.from_date) and gl.posting_date <= getdate(filters.to_date):
data.append(gl)
# Total debit and credit between from and to date
if total_debit or total_credit:
data.append({
"account": "'" + _("Totals") + "'",
"debit": total_debit,
"account": "'" + _("Totals") + "'",
"debit": total_debit,
"credit": total_credit,
"debit_in_account_currency": total_debit_in_account_currency,
"debit_in_account_currency": total_debit_in_account_currency,
"credit_in_account_currency": total_credit_in_account_currency
})
@@ -191,7 +197,7 @@ def get_data_with_opening_closing(filters, account_details, gl_entries):
closing = opening + total_debit - total_credit
closing_in_account_currency = opening_in_account_currency + \
total_debit_in_account_currency - total_credit_in_account_currency
data.append(get_balance_row(_("Closing (Opening + Totals)"),
closing, closing_in_account_currency))
@@ -216,38 +222,38 @@ def initialize_gle_map(gl_entries):
def get_accountwise_gle(filters, gl_entries, gle_map):
opening, total_debit, total_credit = 0, 0, 0
opening_in_account_currency, total_debit_in_account_currency, total_credit_in_account_currency = 0, 0, 0
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
for gle in gl_entries:
amount = flt(gle.debit, 3) - flt(gle.credit, 3)
amount_in_account_currency = flt(gle.debit_in_account_currency, 3) - flt(gle.credit_in_account_currency, 3)
if (filters.get("account") or filters.get("party") or filters.get("group_by_account")) \
and (gle.posting_date < from_date or cstr(gle.is_opening) == "Yes"):
gle_map[gle.account].opening += amount
if filters.get("show_in_account_currency"):
gle_map[gle.account].opening_in_account_currency += amount_in_account_currency
if filters.get("account") or filters.get("party"):
opening += amount
if filters.get("show_in_account_currency"):
opening_in_account_currency += amount_in_account_currency
elif gle.posting_date <= to_date:
gle_map[gle.account].entries.append(gle)
gle_map[gle.account].total_debit += flt(gle.debit, 3)
gle_map[gle.account].total_credit += flt(gle.credit, 3)
total_debit += flt(gle.debit, 3)
total_credit += flt(gle.credit, 3)
if filters.get("show_in_account_currency"):
gle_map[gle.account].total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3)
gle_map[gle.account].total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3)
total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3)
return opening, total_debit, total_credit, opening_in_account_currency, \
total_debit_in_account_currency, total_credit_in_account_currency, gle_map
@@ -258,27 +264,27 @@ def get_balance_row(label, balance, balance_in_account_currency=None):
"debit": balance if balance > 0 else 0,
"credit": -1*balance if balance < 0 else 0
}
if balance_in_account_currency != None:
balance_row.update({
"debit_in_account_currency": balance_in_account_currency if balance_in_account_currency > 0 else 0,
"credit_in_account_currency": -1*balance_in_account_currency if balance_in_account_currency < 0 else 0
})
return balance_row
def get_result_as_list(data, filters):
result = []
for d in data:
row = [d.get("posting_date"), d.get("account"), d.get("debit"), d.get("credit")]
if filters.get("show_in_account_currency"):
row += [d.get("debit_in_account_currency"), d.get("credit_in_account_currency")]
row += [d.get("voucher_type"), d.get("voucher_no"), d.get("against"),
row += [d.get("voucher_type"), d.get("voucher_no"), d.get("against"),
d.get("party_type"), d.get("party"), d.get("cost_center"), d.get("remarks")
]
result.append(row)
return result

View File

@@ -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"
}
]
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -9,6 +9,9 @@ from frappe import throw, _
from frappe.utils import formatdate
import frappe.desk.reportview
# imported to enable erpnext.accounts.utils.get_account_currency
from erpnext.accounts.doctype.account.account import get_account_currency
class FiscalYearError(frappe.ValidationError): pass
class BudgetError(frappe.ValidationError): pass
@@ -94,8 +97,8 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, in_acco
select name from `tabAccount` ac where ac.name = gle.account
and ac.lft >= %s and ac.rgt <= %s
)""" % (acc.lft, acc.rgt))
# If group and currency same as company,
# If group and currency same as company,
# always return balance based on debit and credit in company currency
if acc.account_currency == frappe.db.get_value("Company", acc.company, "default_currency"):
in_account_currency = False
@@ -195,6 +198,8 @@ def update_against_doc(d, jv_obj):
"""
jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0]
jv_detail.set(d["dr_or_cr"], d["allocated_amt"])
jv_detail.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit',
d["allocated_amt"]*flt(jv_detail.exchange_rate))
original_reference_type = jv_detail.reference_type
original_reference_name = jv_detail.reference_name
@@ -207,6 +212,9 @@ def update_against_doc(d, jv_obj):
select cost_center, balance, against_account, is_advance, account_type, exchange_rate
from `tabJournal Entry Account` where name = %s
""", d['voucher_detail_no'], as_dict=True)
amount_in_account_currency = flt(d['unadjusted_amt']) - flt(d['allocated_amt'])
amount_in_company_currency = amount_in_account_currency * flt(jvd[0]['exchange_rate'])
# new entry with balance amount
ch = jv_obj.append("accounts")
@@ -217,8 +225,14 @@ def update_against_doc(d, jv_obj):
ch.party = d["party"]
ch.cost_center = cstr(jvd[0]["cost_center"])
ch.balance = flt(jvd[0]["balance"])
ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt']))
ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0)
ch.set(d['dr_or_cr'], amount_in_account_currency)
ch.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit', amount_in_company_currency)
ch.set('credit_in_account_currency' if d['dr_or_cr']== 'debit_in_account_currency'
else 'debit_in_account_currency', 0)
ch.set('credit' if d['dr_or_cr']== 'debit_in_account_currency' else 'debit', 0)
ch.against_account = cstr(jvd[0]["against_account"])
ch.reference_type = original_reference_type
ch.reference_name = original_reference_name

View File

@@ -528,7 +528,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -1468,6 +1468,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -1478,7 +1479,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nStopped\nCancelled",
"options": "\nDraft\nTo Receive and Bill\nTo Bill\nTo Receive\nCompleted\nStopped\nCancelled",
"permlevel": 0,
"print_hide": 1,
"read_only": 1,
@@ -1704,7 +1705,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "supplied_items",
@@ -2032,7 +2033,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:19:55.502661",
"modified": "2015-10-02 07:17:59.659036",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -36,13 +36,7 @@ class PurchaseOrder(BuyingController):
def validate(self):
super(PurchaseOrder, self).validate()
if not self.status:
self.status = "Draft"
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Draft", "Submitted", "Stopped",
"Cancelled"])
self.set_status()
pc_obj = frappe.get_doc('Purchase Common')
pc_obj.validate_for_items(self)
self.check_for_stopped_status(pc_obj)
@@ -160,12 +154,10 @@ class PurchaseOrder(BuyingController):
def update_status(self, status):
self.check_modified_date()
frappe.db.set(self,'status',cstr(status))
self.db_set('status', status)
self.set_status(update=True)
self.update_requested_qty()
self.update_ordered_qty()
msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status))
self.notify_update()
clear_doctype_notifications(self)
@@ -183,8 +175,6 @@ class PurchaseOrder(BuyingController):
purchase_controller.update_last_purchase_rate(self, is_submit = 1)
frappe.db.set(self,'status','Submitted')
def on_cancel(self):
pc_obj = frappe.get_doc('Purchase Common')
self.check_for_stopped_status(pc_obj)
@@ -238,7 +228,7 @@ def stop_or_unstop_purchase_orders(names, status):
po.update_status("Stopped")
else:
if po.status == "Stopped":
po.update_status("Submitted")
po.update_status("Draft")
frappe.local.message_log = []

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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"
}]
}
]
]

View File

@@ -509,7 +509,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -1554,7 +1554,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:20:10.684388",
"modified": "2015-09-30 08:52:51.539634",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View 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

View File

@@ -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",

View File

@@ -6,16 +6,15 @@ import frappe
from frappe import _, throw
from frappe.utils import today, flt, cint
from erpnext.setup.utils import get_company_currency, get_exchange_rate
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
from erpnext.controllers.sales_and_purchase_return import validate_return
from erpnext.accounts.party import get_party_account_currency, validate_party_gle_currency
from erpnext.exceptions import CustomerFrozen, InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
class CustomerFrozen(frappe.ValidationError): pass
class InvalidCurrency(frappe.ValidationError): pass
class AccountsController(TransactionBase):
def __init__(self, arg1, arg2=None):
super(AccountsController, self).__init__(arg1, arg2)
@@ -169,7 +168,7 @@ class AccountsController(TransactionBase):
if item.price_list_rate:
item.rate = flt(item.price_list_rate *
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
def set_taxes(self):
if not self.meta.get_field("taxes"):
@@ -220,7 +219,7 @@ class AccountsController(TransactionBase):
gl_dict.update(args)
if not account_currency:
account_currency = frappe.db.get_value("Account", gl_dict.account, "account_currency")
account_currency = get_account_currency(gl_dict.account)
if self.doctype != "Journal Entry":
self.validate_account_currency(gl_dict.account, account_currency)
@@ -427,10 +426,12 @@ class AccountsController(TransactionBase):
if self.get("currency"):
party_type, party = self.get_party()
if party_type and party:
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency") \
or self.company_currency
party_account_currency = get_party_account_currency(party_type, party, self.company)
if (party_account_currency
and party_account_currency != self.company_currency
and self.currency != party_account_currency):
if party_account_currency != self.company_currency and self.currency != party_account_currency:
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
.format(party_type, party, party_account_currency), InvalidCurrency)

View File

@@ -30,7 +30,19 @@ status_map = {
],
"Sales Order": [
["Draft", None],
["Submitted", "eval:self.docstatus==1"],
["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
["Stopped", "eval:self.status=='Stopped'"],
["Cancelled", "eval:self.docstatus==2"],
],
"Purchase Order": [
["Draft", None],
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:self.per_received == 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"],
["Stopped", "eval:self.status=='Stopped'"],
["Cancelled", "eval:self.docstatus==2"],
],
@@ -207,10 +219,10 @@ class StatusUpdater(Document):
# update percent complete in the parent table
if args.get('target_parent_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s`
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
set %(target_parent_field)s = round((select sum(if(%(target_ref_field)s >
ifnull(%(target_field)s, 0), %(target_field)s,
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
from `tab%(target_dt)s` where parent="%(name)s") %(set_modified)s
from `tab%(target_dt)s` where parent="%(name)s"), 2) %(set_modified)s
where name='%(name)s'""" % args)
# update field
@@ -222,7 +234,9 @@ class StatusUpdater(Document):
where name='%(name)s'""" % args)
if args.get("set_modified"):
frappe.get_doc(args["target_parent_dt"], name).notify_update()
target = frappe.get_doc(args["target_parent_dt"], name)
target.set_status(update=True)
target.notify_update()
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = ref_dt.lower().replace(" ", "_")

View File

@@ -35,6 +35,8 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
key, parties = "customer", customers
elif suppliers:
key, parties = "supplier", suppliers
else:
key, parties = "customer", []
filters.append((doctype, key, "in", parties))

View File

@@ -58,12 +58,12 @@ class Lead(SellingController):
def check_email_id_is_unique(self):
if self.email_id:
# validate email is unique
email_list = frappe.db.sql("""select name from tabLead where email_id=%s""",
self.email_id)
email_list = [e[0] for e in email_list if e[0]!=self.name]
if len(email_list) > 1:
frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(email_list)),
frappe.DuplicateEntryError)
duplicate_leads = frappe.db.sql_list("""select name from tabLead
where email_id=%s and name!=%s""", (self.email_id, self.name))
if duplicate_leads:
frappe.throw(_("Email id must be unique, already exists for {0}")
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""",

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -29,7 +29,7 @@ blogs.
"""
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "6.3.2"
app_version = "6.4.6"
github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com"
@@ -53,7 +53,7 @@ my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order"]
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order", "Holiday List"]
website_generators = ["Item Group", "Item", "Sales Partner"]
@@ -93,6 +93,16 @@ has_website_permission = {
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission"
}
permission_query_conditions = {
"Contact": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_contact",
"Address": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_address"
}
has_permission = {
"Contact": "erpnext.utilities.address_and_contact.has_permission",
"Address": "erpnext.utilities.address_and_contact.has_permission"
}
dump_report_map = "erpnext.startup.report_data_map.data_map"
before_tests = "erpnext.setup.utils.before_tests"
@@ -128,12 +138,6 @@ scheduler_events = {
"erpnext.support.doctype.issue.issue.auto_close_tickets",
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
"erpnext.hr.doctype.employee.employee.send_birthday_reminders"
],
"daily_long": [
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_daily"
],
"weekly_long": [
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_weekly"
]
}

View File

@@ -16,7 +16,8 @@ class HolidayList(Document):
def get_weekly_off_dates(self):
self.validate_values()
yr_start_date, yr_end_date = self.get_fy_start_end_dates()
self.validate_days()
yr_start_date, yr_end_date = get_fy_start_end_dates(self.fiscal_year)
date_list = self.get_weekly_off_date_list(yr_start_date, yr_end_date)
last_idx = max([cint(d.idx) for d in self.get("holidays")] or [0,])
for i, d in enumerate(date_list):
@@ -30,10 +31,11 @@ class HolidayList(Document):
throw(_("Please select Fiscal Year"))
if not self.weekly_off:
throw(_("Please select weekly off day"))
def get_fy_start_end_dates(self):
return frappe.db.sql("""select year_start_date, year_end_date
from `tabFiscal Year` where name=%s""", (self.fiscal_year,))[0]
def validate_days(self):
for day in self.get("holidays"):
if (self.weekly_off).upper() == (day.description).upper():
frappe.throw("Records alredy exist for mentioned weekly off")
def get_weekly_off_date_list(self, year_start_date, year_end_date):
from frappe.utils import getdate
@@ -59,3 +61,39 @@ class HolidayList(Document):
def update_default_holiday_list(self):
frappe.db.sql("""update `tabHoliday List` set is_default = 0
where ifnull(is_default, 0) = 1 and fiscal_year = %s""", (self.fiscal_year,))
@frappe.whitelist()
def get_events(start, end, filters=None):
import json
"""Returns events for Gantt / Calendar view rendering.
:param start: Start date-time.
:param end: End date-time.
:param filters: Filters (JSON).
"""
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Holiday List", filters)
fiscal_year = None
if filters:
fiscal_year = json.loads(filters).get("fiscal_year")
if not fiscal_year:
fiscal_year = frappe.db.get_value("Global Defaults", None, "current_fiscal_year")
yr_start_date, yr_end_date = get_fy_start_end_dates(fiscal_year)
data = frappe.db.sql("""select hl.name, hld.holiday_date, hld.description
from `tabHoliday List` hl, tabHoliday hld
where hld.parent = hl.name
and (ifnull(hld.holiday_date, "0000-00-00") != "0000-00-00"
and hld.holiday_date between %(start)s and %(end)s)
{conditions}""".format(conditions=conditions), {
"start": yr_start_date,
"end": yr_end_date
}, as_dict=True, update={"allDay": 1})
return data
def get_fy_start_end_dates(fiscal_year):
return frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"])

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.views.calendar["Holiday List"] = {
field_map: {
"start": "holiday_date",
"end": "holiday_date",
"id": "name",
"title": "description",
"allDay": "allDay"
},
filters: [
{
"fieldtype": "Link",
"fieldname": "fiscal_year",
"options": "Fiscal Year",
"label": __("Fiscal Year")
}
],
get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events"
}

View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, cint
from frappe import msgprint, _
from calendar import monthrange
def execute(filters=None):
if not filters: filters = {}
@@ -73,23 +74,17 @@ def get_conditions(filters):
msgprint(_("Please select month and year"), raise_exception=1)
filters["month"] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
"Dec"].index(filters["month"]) + 1
"Dec"].index(filters.month) + 1
from frappe.model.document import Document
fiscal_years = frappe.get_doc("Fiscal Year",filters["fiscal_year"])
import datetime
year_start = fiscal_years.year_start_date.strftime("%Y")
year_end = fiscal_years.year_end_date.strftime("%Y")
dt_test = datetime.datetime.strptime(year_end + "-" + str(100+int(filters["month"]))[2:3] + "-01", "%Y-%m-%d")
date_test = datetime.date(dt_test.year, dt_test.month, dt_test.day)
if date_test > fiscal_years.year_end_date:
year_target = year_start
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year", filters.fiscal_year,
["year_start_date", "year_end_date"])
if filters.month >= year_start_date.strftime("%m"):
year = year_start_date.strftime("%Y")
else:
year_target = year_end
from calendar import monthrange
filters["total_days_in_month"] = monthrange(cint(year_target),
filters["month"])[1]
year = year_end_date.strftime("%Y")
filters["total_days_in_month"] = monthrange(cint(year), filters.month)[1]
conditions = " and month(att_date) = %(month)s and fiscal_year = %(fiscal_year)s"

View File

@@ -391,8 +391,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1):
if fetch_exploded:
query = query.format(table="BOM Explosion Item",
conditions="""and item.is_pro_applicable = 0
and item.is_sub_contracted_item = 0 """)
conditions="""and item.is_sub_contracted_item = 0""")
items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
else:
query = query.format(table="BOM Item", conditions="")

View File

@@ -211,3 +211,8 @@ erpnext.patches.v6_2.remove_newsletter_duplicates
erpnext.patches.v6_2.fix_missing_default_taxes_and_lead
erpnext.patches.v5_8.tax_rule
erpnext.patches.v6_3.convert_applicable_territory
erpnext.patches.v6_4.round_status_updater_percentages
erpnext.patches.v6_4.repost_gle_for_journal_entries_where_reference_name_missing
erpnext.patches.v6_4.fix_journal_entries_due_to_reconciliation
erpnext.patches.v6_4.fix_status_in_sales_and_purchase_order
erpnext.patches.v6_4.fix_modified_in_sales_order_and_purchase_order

View File

@@ -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()

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -0,0 +1,50 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
je_rows = frappe.db.sql("""
select name, parent, reference_type, reference_name, debit, credit
from `tabJournal Entry Account`
where docstatus=1 and date(modified) >= '2015-09-17'
and ((ifnull(debit_in_account_currency, 0)*exchange_rate != ifnull(debit, 0))
or (ifnull(credit_in_account_currency, 0)*exchange_rate != ifnull(credit, 0)))
order by parent
""", as_dict=True)
journal_entries = []
for d in je_rows:
if d.parent not in journal_entries:
journal_entries.append(d.parent)
is_advance_entry=None
if d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name:
is_advance_entry = frappe.db.sql("""select name from `tab{0}`
where journal_entry=%s and jv_detail_no=%s
and ifnull(allocated_amount, 0) > 0 and docstatus=1"""
.format(d.reference_type + " Advance"), (d.parent, d.name))
if is_advance_entry or not (d.debit or d.credit):
frappe.db.sql("""
update `tabJournal Entry Account`
set debit=debit_in_account_currency*exchange_rate,
credit=credit_in_account_currency*exchange_rate
where name=%s""", d.name)
else:
frappe.db.sql("""
update `tabJournal Entry Account`
set debit_in_account_currency=debit/exchange_rate,
credit_in_account_currency=credit/exchange_rate
where name=%s""", d.name)
for d in journal_entries:
print d
# delete existing gle
frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d)
# repost gl entries
je = frappe.get_doc("Journal Entry", d)
je.make_gl_entries()

View File

@@ -0,0 +1,10 @@
from __future__ import unicode_literals
import frappe
def execute():
for doctype in ("Sales Order", "Purchase Order"):
data = frappe.db.sql("""select parent, modified_by, modified
from `tab{doctype} Item` where docstatus=1 group by parent""".format(doctype=doctype), as_dict=True)
for item in data:
frappe.db.sql("""update `tab{doctype}` set modified_by=%(modified_by)s, modified=%(modified)s
where name=%(parent)s""".format(doctype=doctype), item)

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
for doctype in ("Sales Order", "Purchase Order"):
for doc in frappe.get_all(doctype, filters={"docstatus": 1}):
doc = frappe.get_doc(doctype, doc.name)
doc.set_status(update=True)

View File

@@ -0,0 +1,23 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
je_list = frappe.db.sql_list("""select distinct parent from `tabJournal Entry Account` je
where docstatus=1 and ifnull(reference_name, '') !='' and creation > '2015-03-01'
and not exists(select name from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no=je.parent
and against_voucher_type=je.reference_type
and against_voucher=je.reference_name)""")
for d in je_list:
print d
# delete existing gle
frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d)
# repost gl entries
je = frappe.get_doc("Journal Entry", d)
je.make_gl_entries()

View 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))

View File

@@ -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() {

View File

@@ -10,6 +10,7 @@ frappe.views.calendar["Time Log"] = {
"allDay": "allDay"
},
gantt: true,
gantt_scale: "hours",
filters: [
{
"fieldtype": "Link",

View File

@@ -34,7 +34,7 @@ class TimeLogBatch(Document):
def validate_time_log_is_submitted(self, tl):
if tl.status == "Batched for Billing":
frappe.throw(_("Time Log {0} already billed").format(tl.name))
elif tl.status != "Submitted":
elif tl.docstatus != 1:
frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name))
def set_status(self):

View File

@@ -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);

View File

@@ -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...'));
});

View File

@@ -17,7 +17,6 @@ frappe.ui.form.on("Customer", "refresh", function(frm) {
}
var grid = cur_frm.get_field("sales_team").grid;
grid.set_column_disp("allocated_percentage", false);
grid.set_column_disp("allocated_amount", false);
grid.set_column_disp("incentives", false);
@@ -101,11 +100,11 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d
'company': d.company,
"is_group": 0
};
if(doc.party_account_currency) {
$.extend(filters, {"account_currency": doc.party_account_currency});
}
return {
filters: filters
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,10 @@ from frappe.model.naming import make_autoname
from frappe import _, msgprint, throw
import frappe.defaults
from frappe.utils import flt
from frappe.desk.reportview import build_match_conditions
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.utilities.address_and_contact import load_address_and_contact
from erpnext.accounts.party import validate_accounting_currency, validate_party_account
from frappe.desk.reportview import build_match_conditions
from erpnext.accounts.party import validate_party_accounts
class Customer(TransactionBase):
def get_feed(self):
@@ -33,8 +32,7 @@ class Customer(TransactionBase):
def validate(self):
self.flags.is_new_doc = self.is_new()
validate_accounting_currency(self)
validate_party_account(self)
validate_party_accounts(self)
def update_lead_status(self):
if self.lead_name:
@@ -197,27 +195,26 @@ def get_customer_outstanding(customer, company):
outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
# Outstanding based on Delivery Note
outstanding_based_on_dn = frappe.db.sql("""
select
sum(
(
(ifnull(dn_item.amount, 0) - ifnull((select sum(ifnull(amount, 0))
from `tabSales Invoice Item`
where ifnull(dn_detail, '') = dn_item.name and docstatus = 1), 0)
)/dn.base_net_total
)*dn.base_grand_total
)
unmarked_delivery_note_items = frappe.db.sql("""select
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
where
dn.name = dn_item.parent and dn.customer=%s and dn.company=%s
dn.name = dn_item.parent
and dn.customer=%s and dn.company=%s
and dn.docstatus = 1 and dn.status != 'Stopped'
and ifnull(dn_item.against_sales_order, '') = ''
and ifnull(dn_item.against_sales_invoice, '') = ''
and ifnull(dn_item.amount, 0) > ifnull((select sum(ifnull(amount, 0))
from `tabSales Invoice Item`
where ifnull(dn_detail, '') = dn_item.name and docstatus = 1), 0)""", (customer, company))
and ifnull(dn_item.against_sales_invoice, '') = ''""", (customer, company), as_dict=True)
outstanding_based_on_dn = flt(outstanding_based_on_dn[0][0]) if outstanding_based_on_dn else 0.0
outstanding_based_on_dn = 0.0
for dn_item in unmarked_delivery_note_items:
si_amount = frappe.db.sql("""select sum(ifnull(amount, 0))
from `tabSales Invoice Item`
where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
/ dn_item.base_net_total) * dn_item.base_grand_total
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn

View File

@@ -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)

View File

@@ -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"

View File

@@ -613,7 +613,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -1843,7 +1843,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
"modified": "2015-09-11 12:20:18.521489",
"modified": "2015-09-30 08:52:54.426175",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,27 @@ form_grid_templates = {
class WarehouseRequired(frappe.ValidationError): pass
class SalesOrder(SellingController):
def validate(self):
super(SalesOrder, self).validate()
self.validate_order_type()
self.validate_delivery_date()
self.validate_mandatory()
self.validate_proj_cust()
self.validate_po()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_for_items()
self.validate_warehouse()
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self,'items')
self.validate_with_previous_doc()
self.set_status()
if not self.billing_status: self.billing_status = 'Not Billed'
if not self.delivery_status: self.delivery_status = 'Not Delivered'
def validate_mandatory(self):
# validate transaction date v/s delivery date
if self.delivery_date:
@@ -93,33 +114,6 @@ class SalesOrder(SellingController):
if not res:
frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project_name))
def validate(self):
super(SalesOrder, self).validate()
self.validate_order_type()
self.validate_delivery_date()
self.validate_mandatory()
self.validate_proj_cust()
self.validate_po()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_for_items()
self.validate_warehouse()
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self,'items')
self.validate_with_previous_doc()
if not self.status:
self.status = "Draft"
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Draft", "Submitted", "Stopped",
"Cancelled"])
if not self.billing_status: self.billing_status = 'Not Billed'
if not self.delivery_status: self.delivery_status = 'Not Delivered'
def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company
@@ -162,7 +156,6 @@ class SalesOrder(SellingController):
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.base_grand_total, self)
self.update_prevdoc_status('submit')
frappe.db.set(self, 'status', 'Submitted')
def on_cancel(self):
# Cannot cancel stopped SO
@@ -175,7 +168,7 @@ class SalesOrder(SellingController):
self.update_prevdoc_status('cancel')
frappe.db.set(self, 'status', 'Cancelled')
def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit
check_credit_limit(self.customer, self.company)
@@ -223,17 +216,16 @@ class SalesOrder(SellingController):
def stop_sales_order(self):
self.check_modified_date()
frappe.db.set(self, 'status', 'Stopped')
self.db_set('status', 'Stopped')
self.update_reserved_qty()
frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name))
self.notify_update()
clear_doctype_notifications(self)
def unstop_sales_order(self):
self.check_modified_date()
frappe.db.set(self, 'status', 'Submitted')
self.db_set('status', 'Draft')
self.set_status(update=True)
self.update_reserved_qty()
frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name))
clear_doctype_notifications(self)
def update_reserved_qty(self, so_item_rows=None):

View File

@@ -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

View File

@@ -1,16 +1,18 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-06-21 16:46:45",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2015-03-30 05:45:40.146567",
"modified": "2015-10-06 12:43:48.259027",
"modified_by": "Administrator",
"module": "Selling",
"name": "Pending SO Items For Purchase Request",
"owner": "Administrator",
"query": "select \n so_item.item_code as \"Item Code:Link/Item:120\",\n so_item.item_name as \"Item Name::120\",\n so_item.description as \"Description::120\",\n so.`name` as \"S.O. No.:Link/Sales Order:120\",\n so.`transaction_date` as \"Date:Date:120\",\n mr.name as \"Material Request:Link/Material Request:120\",\n so.customer as \"Customer:Link/Customer:120\",\n so.territory as \"Terretory:Link/Territory:120\",\n sum(so_item.qty) as \"SO Qty:Float:100 \",\n sum(mr_item.qty) as \"Requested Qty:Float:100\",\n so.company as \"Company:Link/Company:\"\nfrom\n `tabSales Order` so, `tabSales Order Item` so_item, \n `tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere\n so_item.`parent` = so.`name` and mr_item.sales_order_no = so.name\n and mr_item.parent = mr.name \n and so.docstatus = 1 and so.status != \"Stopped\" \n and mr.docstatus = 1 and mr.status != \"Stopped\"\ngroup by so.name, so_item.item_code\norder by so.name desc, so_item.item_code asc",
"query": "select so_item.item_code as \"Item Code:Link/Item:120\",\n so_item.item_name as \"Item Name::120\",\n so_item.description as \"Description::120\",\n so.`name` as \"S.O. No.:Link/Sales Order:120\",\n so.`transaction_date` as \"Date:Date:120\",\n mr.name as \"Material Request:Link/Material Request:120\",\n so.customer as \"Customer:Link/Customer:120\",\n so.territory as \"Terretory:Link/Territory:120\",\n sum(so_item.qty) as \"SO Qty:Float:100 \",\n sum(mr_item.qty) as \"Requested Qty:Float:100\",\n sum(so_item.qty) - sum(mr_item.qty) as \"Pending Qty:Float:100 \", \n so.company as \"Company:Link/Company:\"\nfrom\n `tabSales Order` so, `tabSales Order Item` so_item, \n `tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere \n so_item.`parent` = so.`name` \n and mr_item.parent = mr.name\n and mr_item.sales_order_no = so.name\n and mr_item.item_code = so_item.item_code\n and so.docstatus = 1 and so.status != \"Stopped\" \n and mr.docstatus = 1 and mr.status != \"Stopped\"\ngroup by so.name, so_item.item_code\nhaving sum(so_item.qty) > sum(mr_item.qty)\norder by so.name desc, so_item.item_code asc",
"ref_doctype": "Sales Order",
"report_name": "Pending SO Items For Purchase Request",
"report_type": "Query Report"

View File

@@ -1 +0,0 @@
Settings to manage automated backups to third party tools like Dropbox and Google Drive.

View File

@@ -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()

View File

@@ -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>

View File

@@ -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()

View File

@@ -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();
// },
});

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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):

View File

@@ -69,12 +69,13 @@ class NamingSeries(Document):
# update in property setter
prop_dict = {'options': "\n".join(options), 'default': default}
for prop in prop_dict:
ps_exists = frappe.db.sql("""SELECT name FROM `tabProperty Setter`
WHERE doc_type = %s AND field_name = 'naming_series'
AND property = %s""", (doctype, prop))
ps_exists = frappe.db.get_value("Property Setter",
{"field_name": 'naming_series', 'doc_type': doctype, 'property': prop})
if ps_exists:
ps = frappe.get_doc('Property Setter', ps_exists[0][0])
ps = frappe.get_doc('Property Setter', ps_exists)
ps.value = prop_dict[prop]
ps.save()
else:
@@ -180,6 +181,9 @@ def get_default_naming_series(doctype):
naming_series = frappe.get_meta(doctype).get_field("naming_series").options or ""
naming_series = naming_series.split("\n")
out = naming_series[0] or (naming_series[1] if len(naming_series) > 1 else None)
if out:
if not out:
frappe.throw(_("Please set Naming Series for {0} via Setup > Settings > Naming Series").format(doctype),
NamingSeriesNotSetError)
else:
return out

View File

@@ -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")

View File

@@ -4,8 +4,9 @@
from __future__ import unicode_literals
import frappe
from frappe.utils.make_random import add_random_children, get_random
from frappe.utils.make_random import add_random_children
import frappe.utils
import random
def make_sample_data():
"""Create a few opportunities, quotes, material requests, issues, todos, projects
@@ -13,11 +14,13 @@ def make_sample_data():
selling_items = frappe.get_all("Item", filters = {"is_sales_item": 1})
buying_items = frappe.get_all("Item", filters = {"is_purchase_item": 1})
if selling_items:
customers = frappe.get_all("Customer")
if selling_items and customers:
for i in range(3):
make_opportunity(selling_items)
make_quote(selling_items)
customer = random.choice(customers).name
make_opportunity(selling_items, customer)
make_quote(selling_items, customer)
make_projects()
@@ -26,11 +29,11 @@ def make_sample_data():
frappe.db.commit()
def make_opportunity(selling_items):
def make_opportunity(selling_items, customer):
b = frappe.get_doc({
"doctype": "Opportunity",
"enquiry_from": "Customer",
"customer": get_random("Customer"),
"customer": customer,
"enquiry_type": "Sales",
"with_items": 1
})
@@ -44,11 +47,11 @@ def make_opportunity(selling_items):
b.add_comment("This is a dummy record")
def make_quote(selling_items):
def make_quote(selling_items, customer):
qtn = frappe.get_doc({
"doctype": "Quotation",
"quotation_to": "Customer",
"customer": get_random("Customer"),
"customer": customer,
"order_type": "Sales"
})

View File

@@ -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);

View File

@@ -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()

View File

@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def get_notification_config():
return { "for_doctype":
@@ -15,7 +14,11 @@ def get_notification_config():
"Contact": {"status": "Open"},
"Opportunity": {"status": "Open"},
"Quotation": {"docstatus": 0},
"Sales Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
"Sales Order": {
"status": ("!=", "Stopped"),
"status": ("!=", "Completed"),
"docstatus": ("<", 2)
},
"Journal Entry": {"docstatus": 0},
"Sales Invoice": { "outstanding_amount": (">", 0), "docstatus": ("<", 2) },
"Purchase Invoice": {"docstatus": 0},
@@ -26,7 +29,11 @@ def get_notification_config():
"Delivery Note": {"docstatus": 0},
"Stock Entry": {"docstatus": 0},
"Material Request": {"docstatus": 0},
"Purchase Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
"Purchase Order": {
"status": ("!=", "Completed"),
"status": ("!=", "Stopped"),
"docstatus": ("<", 2)
},
"Production Order": { "status": "In Process" },
"BOM": {"docstatus": 0},
"Timesheet": {"docstatus": 0},

View File

@@ -699,7 +699,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -2438,7 +2438,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-09-23 09:54:33.751001",
"modified": "2015-09-30 08:52:57.917732",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -561,7 +561,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "items",
@@ -2076,7 +2076,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:20:13.894374",
"modified": "2015-09-30 08:52:54.964591",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@@ -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"],

View File

@@ -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"):

View File

@@ -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",

View File

@@ -200,7 +200,8 @@ def get_default_expense_account(args, item):
def get_default_cost_center(args, item):
return (frappe.db.get_value("Project", args.get("project_name"), "cost_center")
or (item.selling_cost_center if args.get("transaction_type") == "selling" else item.buying_cost_center)
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center"))
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
or args.get("cost_center"))
def get_price_list_rate(args, item_doc, out):
meta = frappe.get_meta(args.parenttype)
@@ -345,8 +346,7 @@ def get_conversion_factor(item_code, uom):
variant_of = frappe.db.get_value("Item", item_code, "variant_of")
filters = {"parent": item_code, "uom": uom}
if variant_of:
filters = {"parent": ("in", (item_code, variant_of))}
filters["parent"] = ("in", (item_code, variant_of))
return {"conversion_factor": frappe.db.get_value("UOM Conversion Detail",
filters, "conversion_factor")}

View File

@@ -1,16 +1,18 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-02-22 18:01:55",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2015-06-10 15:52:49.492144",
"modified": "2015-10-06 12:43:37.547654",
"modified_by": "Administrator",
"module": "Stock",
"name": "Ordered Items To Be Delivered",
"owner": "Administrator",
"query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project_name` as \"Project\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order`.`delivery_date` as \"Expected Delivery Date:Date:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order`, `tabSales Order Item`, `tabBin`\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status != \"Stopped\"\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\n and `tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse\norder by `tabSales Order`.transaction_date asc",
"query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project_name` as \"Project\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order`.`delivery_date` as \"Expected Delivery Date:Date:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status != \"Stopped\"\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc",
"ref_doctype": "Delivery Note",
"report_name": "Ordered Items To Be Delivered",
"report_type": "Query Report"

View File

@@ -93,14 +93,14 @@ def get_item_warehouse_map(filters):
qty_dict.opening_qty += qty_diff
qty_dict.opening_val += value_diff
elif d.posting_date >= getdate(filters["from_date"]) and d.posting_date <= getdate(filters["to_date"]):
qty_dict.val_rate = d.valuation_rate
if qty_diff > 0:
qty_dict.in_qty += qty_diff
qty_dict.in_val += value_diff
else:
qty_dict.out_qty += abs(qty_diff)
qty_dict.out_val += abs(value_diff)
qty_dict.val_rate = d.valuation_rate
qty_dict.bal_qty += qty_diff
qty_dict.bal_val += value_diff

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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)

View File

@@ -1 +1,2 @@
DocType: Account,Accounts,དངུལ་རྩིས།
DocType: Pricing Rule,Buying,ཉོ་བ།
1 DocType: Account Accounts དངུལ་རྩིས།
2 DocType: Pricing Rule Buying ཉོ་བ།

File diff suppressed because it is too large Load Diff

View File

@@ -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&#39;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 &quot;service&quot;
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
1 DocType: Employee Salary Mode Mode de rémunération
37 DocType: Purchase Order % Billed Facturé%
38 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})
39 DocType: Sales Invoice Customer Name Nom du client
40 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 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
41 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.
42 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é
43 DocType: Manufacturing Settings Default 10 mins Par défaut 10 minutes
337 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 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
338 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é
339 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é
340 apps/erpnext/erpnext/config/hr.py +110 Employee designation (e.g. CEO, Director etc.). Vous devez enregistrer le formulaire avant de procéder
341 apps/erpnext/erpnext/controllers/recurring_document.py +200 Please enter 'Repeat on Day of Month' field value 1 devise = [ ? ] Fraction
342 DocType: Sales Invoice Rate at which Customer Currency is converted to customer's base currency Vitesse à laquelle la devise du client est converti en devise de base du client
343 DocType: Features Setup Available in BOM, Delivery Note, Purchase Invoice, Production Order, Purchase Order, Purchase Receipt, Sales Invoice, Sales Order, Stock Entry, Timesheet Disponible en nomenclature , bon de livraison , facture d'achat , ordre de production, bon de commande , bon de réception , la facture de vente , Sales Order , Stock entrée , des feuilles de temps
392 DocType: Account Cost of Goods Sold Montant payé + Write Off montant ne peut être supérieur à Total
393 DocType: Purchase Invoice Yearly Annuel
394 apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +223 Please enter Cost Center S'il vous plaît entrer Centre de coûts
395 DocType: Sales Invoice Item Sales Order Commande
396 apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py +63 Avg. Selling Rate Moy. Taux de vente
397 DocType: Purchase Order Start date of current order's period Date de la période de l'ordre courant de démarrage
398 apps/erpnext/erpnext/utilities/transaction_base.py +128 Quantity cannot be a fraction in row {0} Quantité ne peut pas être une fraction de la rangée
1901 DocType: Employee Education Post Graduate Message d&#39;études supérieures
1902 DocType: Backup Manager Note: Backups and files are not deleted from Dropbox, you will have to delete them manually. Remarque: Les sauvegardes et les fichiers ne sont pas effacés de Dropbox, vous devrez supprimer manuellement.
1903 DocType: Maintenance Schedule Detail Maintenance Schedule Detail Détail calendrier d&#39;entretien
1904 DocType: Quality Inspection Reading Reading 9 Lecture 9
1905 DocType: Supplier Is Frozen Est Gelé
1906 DocType: Buying Settings Buying Settings Réglages d&#39;achat
1907 apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/operations.py +121 Mass finishing La finition de masse
2427 DocType: Production Plan Sales Order SO Date SO Date
2428 apps/erpnext/erpnext/stock/doctype/manage_variants/manage_variants.py +77 Attribute value {0} does not exist in Item Attribute Master. Valeur de l&#39;attribut {0} n&#39;&#39;existe pas dans l&#39;article Attribut Maître.
2429 DocType: Sales Invoice Rate at which Price list currency is converted to customer's base currency Taux auquel la monnaie Liste de prix est converti en devise de base du client
2430 DocType: Purchase Invoice Item Net Amount (Company Currency) Montant net (Société devise)
2431 DocType: BOM Operation Hour Rate Taux horraire
2432 DocType: Stock Settings Item Naming By Point de noms en
2433 apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.js +637 From Quotation De offre
3917
3918
3919
3920
3921
3922
3923

Some files were not shown because too many files have changed in this diff Show More