Compare commits

..

126 Commits

Author SHA1 Message Date
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
Anand Doshi
52efde31e7 Merge branch 'develop' 2015-09-24 17:31:12 +05:30
Anand Doshi
f723032fd7 bumped to version 6.3.2 2015-09-24 18:01:11 +06:00
Anand Doshi
3297c43bdf [fix] Tax Rule permission to Account Manager 2015-09-24 17:29:28 +05:30
Anand Doshi
aea250bc5a [fix] [patch] fix missing default lead 2015-09-24 16:31:36 +05:30
Anand Doshi
d37d4dfdec [fix] applicable territory patch and tax rule tests 2015-09-24 15:44:39 +05:30
Anand Doshi
0ea68b33ed Merge branch 'develop' 2015-09-24 15:23:05 +05:30
Anand Doshi
7f96c20f5b bumped to version 6.3.1 2015-09-24 15:53:05 +06:00
Anand Doshi
e75d947867 [fix] [patch] convert applicable territory 2015-09-24 15:22:12 +05:30
Anand Doshi
bba0a5d38f [fix] [patch] tax rule 2015-09-24 15:11:24 +05:30
Anand Doshi
953e97536a [fix] [patch] tax rule 2015-09-24 15:05:06 +05:30
Anand Doshi
e74d7ca33e [fix] [patch] tax rule 2015-09-24 15:04:15 +05:30
Rushabh Mehta
8f2b8afcb7 [fix] gl entry report if not filtered 2015-09-24 15:03:53 +05:30
Anand Doshi
b3f12c3109 [fix] [patch] convert_applicable_territory 2015-09-24 15:01:27 +05:30
Anand Doshi
f66e6aacd4 [fix] [patch] convert_applicable_territory 2015-09-24 14:59:26 +05:30
Anand Doshi
1d621be1f7 Merge branch 'develop' 2015-09-24 14:35:36 +05:30
Anand Doshi
ab57e52cbd bumped to version 6.3.0 2015-09-24 15:05:35 +06:00
Anand Doshi
e63da9813c Merge remote-tracking branch 'frappe/master' into develop
Conflicts:
	erpnext/patches.txt
2015-09-24 14:34:50 +05:30
Anand Doshi
c020e42d20 [fix] [patch] convert applicable territory 2015-09-24 13:08:35 +05:30
Anand Doshi
b126ba0132 [change-log] 2015-09-24 13:06:00 +05:30
Anand Doshi
20ae349ebd Merge pull request #4055 from anandpdoshi/project-sales-order-query
Project's Sales Order - get_query fix
2015-09-24 12:49:54 +05:30
Anand Doshi
c6e4b5978d [fix] Sales Order - get_query fix 2015-09-24 11:32:54 +05:30
Anand Doshi
361eca4cae Merge pull request #4054 from anandpdoshi/sales-team-update-after-submit
Sales Team editable after submit
2015-09-24 11:30:31 +05:30
Anand Doshi
9fc03b6c10 Merge pull request #4052 from rmehta/fix-operation-name
[fix] minor, remove operation naming field from operation
2015-09-24 11:30:07 +05:30
Anand Doshi
e2d46d0474 [fix] Allow Sales Team to be added/updated after submit 2015-09-23 19:26:29 +05:30
Rushabh Mehta
dd2f2f5321 [fix] minor, remove operation naming field from operation 2015-09-23 17:06:01 +05:30
Anand Doshi
6e4f5a214a Merge pull request #4033 from rmehta/portal-fixes
Portal Fixes
2015-09-23 16:05:12 +05:30
Anand Doshi
b07b0a9e54 [fix] Sales Order indicators for Maintenance 2015-09-23 15:52:11 +05:30
Rushabh Mehta
307978fea9 [test-fixes] 2015-09-23 15:43:09 +05:30
Anand Doshi
06ad308ca1 [fixes] setup wizard and other fixes related to cart 2015-09-23 12:50:35 +05:30
Anand Doshi
52dfc32eca [fix] validate_valuation_rate for Repack 2015-09-23 12:50:35 +05:30
Rushabh Mehta
72fbf902d7 [cleanup] added single price list for shopping cart, removed Applicable Territory 2015-09-23 12:50:34 +05:30
Rushabh Mehta
8ffd483e24 [wip] shopping cart shipping rule, price list cleanup 2015-09-23 12:49:16 +05:30
Rushabh Mehta
3d76686b82 [shopping-cart] cart via Jinja WIP 2015-09-23 12:49:16 +05:30
Rushabh Mehta
156ce607e2 [cleanup] [wip] portal, shopping cart cleanup 2015-09-23 12:49:16 +05:30
Anand Doshi
65b6762247 Merge pull request #4049 from bobzz-zone/patch-11
Update item_grid.html
2015-09-23 12:09:26 +05:30
Rushabh Mehta
abdfb4d3db [fixed] New button in POS Sales Invoice, reported via forum 2015-09-23 12:05:51 +05:30
bobzz-zone
3bcb13b1b8 Update item_grid.html
Change the colour of the indicator to be green when delivered qty is bigger than ordered qty
2015-09-23 11:42:06 +07:00
Anand Doshi
b6ec680c46 [fix] round percentages in get_indicator 2015-09-22 16:36:20 +05:30
Anand Doshi
53a0de7607 [fix] Recalculate rate after applying Pricing Rule 2015-09-22 15:26:53 +05:30
Anand Doshi
0a1d037f01 Merge pull request #3974 from rmehta/dms
[enhancement] document management system
2015-09-22 14:51:01 +05:30
Anand Doshi
ce3e15d30c Merge pull request #4042 from anandpdoshi/fix-payment-reconciliation-invoice
[fix] Show payment's invoice number as 'Invoice Type | Invoice Number' for better unsability
2015-09-22 14:21:23 +05:30
Nabin Hait
5b649521d1 [fix] Get outstanding invoices in Payment Reconciliation and Payment Tool 2015-09-22 13:06:34 +05:30
Anand Doshi
798e75832c [fix] Show payment's invoice number as 'Invoice Type | Invoice Number' for better unsability 2015-09-22 12:59:43 +05:30
Anand Doshi
aa95a1b1ef Merge pull request #4047 from nabinhait/payment_reco
[fix] Get outstanding invoices in Payment Reconciliation and Payment Tool
2015-09-22 12:55:27 +05:30
Nabin Hait
c0c94aef44 [fix] Get outstanding invoices in Payment Reconciliation and Payment Tool 2015-09-22 08:37:37 +05:30
Anand Doshi
e4dfeb651f [optimization] get balance on 2015-09-21 19:31:43 +05:30
Anand Doshi
f7e6eb4fd7 [optimization] get balance on 2015-09-21 19:30:21 +05:30
Rushabh Mehta
97c858a5e3 [fix] pull sales team from customer 2015-09-21 10:03:38 +05:30
Rushabh Mehta
974892bf87 Merge pull request #4036 from rmehta/no-party-currency
[fix] no default party currency
2015-09-18 13:40:42 +05:30
Rushabh Mehta
361df8993f [fix] no default party currency 2015-09-18 12:59:51 +05:30
Anand Doshi
be6cfddc4d [hotfix] multicurrency gl entry 2015-09-17 21:07:04 +05:30
Anand Doshi
bf8c8df9ce Revert "[hotfix] set_balance_in_account_currency"
This reverts commit 9acd6a2629.
2015-09-17 20:55:50 +05:30
Anand Doshi
26bcd89d10 [hotfix] journal entry - get balance 2015-09-17 20:36:44 +05:30
Anand Doshi
9acd6a2629 [hotfix] set_balance_in_account_currency 2015-09-17 20:29:31 +05:30
Anand Doshi
779ae439cd Merge pull request #4034 from nabinhait/mc5
[fix] minor issue
2015-09-17 20:10:19 +05:30
Nabin Hait
a8ef4c9220 [fix] minor issue 2015-09-17 19:38:11 +05:30
Anand Doshi
0332f83bc2 [fix] customer naming series validation and patch to fix missing default taxes and lead 2015-09-17 19:01:48 +05:30
Nabin Hait
926ae17e5a Update journal_entry.py 2015-09-17 18:42:55 +05:30
Anand Doshi
5532a14938 [hotfix] create customer contact and address from lead only on create 2015-09-17 18:27:56 +05:30
Anand Doshi
e9b14e497b [fix] remove duplicate newsletter subscribers 2015-09-17 17:46:40 +05:30
Anand Doshi
cba5a684cb [hotfix] multicurrency patch 2015-09-17 17:36:05 +05:30
Anand Doshi
340709b2da [hotfix] multicurrency patch 2015-09-17 17:28:39 +05:30
Anand Doshi
28386f551b Merge pull request #4032 from nabinhait/mc4
[fix] Multi currency patch
2015-09-17 17:16:08 +05:30
Nabin Hait
9ff52cd57e [fix] Multi currency patch 2015-09-17 17:07:26 +05:30
Rushabh Mehta
e3401182c8 Merge pull request #4012 from saurabh6790/cart
Taxation for Shopping Cart based on Tax Rule template
2015-09-17 16:26:35 +05:30
Saurabh
0bd145a608 [fixes] test cases and tax rule validation 2015-09-15 17:06:52 +05:30
Saurabh
c663f5c2bd [Test] test state based taxasion 2015-09-15 15:41:01 +05:30
Saurabh
adde1cff48 [Enhancement] add state ccriteria in tax rule 2015-09-15 15:41:01 +05:30
Saurabh
bb9427d1ef [Fixes] deprecated 2015-09-15 15:41:01 +05:30
Saurabh
052babc6b6 [Fixes] [Minor] removed console statement 2015-09-15 15:41:01 +05:30
Saurabh
24fa06bc53 [Fixes] rebase conflict fixes 2015-09-15 15:41:01 +05:30
Saurabh
7c867ae9ad [Fixes] Tax Rule preparation and test taxes creation, remove dummy quotation 2015-09-15 15:41:01 +05:30
Saurabh
8f7317175d [enhancement] bring taxes from tax rules and test cases 2015-09-15 15:41:01 +05:30
Saurabh
957e7a37be [Shopping Cart][Fixes] tax calculation based on tax rule 2015-09-15 15:41:01 +05:30
Saurabh
def71d4d5d [Shopping Cart][Fixes] tax calculation based on tax rule 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
ef511b160e Added Tax Rule under ERPNext > Accounts > Setup 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
09f9c96c53 Fixes in Tax Rule 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
1a2d121073 Removed default taxes and charges from customer and supplier master. Added patch to create tax rules against customer/supplier 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
810bd35609 Apply Tax Rule based on Customer Selection in Sales / Purchase Transactions 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
949d7dbaba Added validations, test-cases to Tax Rule 2015-09-15 15:41:00 +05:30
Neil Trini Lasrado
72e6aa160c Added New Doctype Tax Rule 2015-09-15 15:41:00 +05:30
Rushabh Mehta
deb38f7a68 [enhancement] document management system 2015-09-02 18:34:24 +05:30
162 changed files with 12088 additions and 12004 deletions

View File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = '6.2.0'
__version__ = '6.4.0'

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,14 @@ def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
and %s like %s order by name limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
def get_account_currency(account):
"""Helper function to get account currency"""
def generator():
account_currency, company = frappe.db.get_value("Account", account, ["account_currency", "company"])
if not account_currency:
account_currency = frappe.db.get_value("Company", company, "default_currency")
return account_currency
return frappe.local_cache("account_currency", account, generator)

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

@@ -330,7 +330,7 @@
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@@ -511,7 +511,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 15:51:26",
"modified": "2015-09-21 15:51:26",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",

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

@@ -6,7 +6,8 @@ import frappe
from frappe.utils import cstr, flt, fmt_money, formatdate
from frappe import msgprint, _, scrub
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.utils import get_balance_on
from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.accounts.party import get_party_account_currency
from erpnext.setup.utils import get_company_currency
@@ -146,7 +147,7 @@ class JournalEntry(AccountsController):
self.reference_totals = {}
self.reference_types = {}
self.reference_parties = {}
self.reference_accounts = {}
for d in self.get("accounts"):
if not d.reference_type:
@@ -169,8 +170,7 @@ class JournalEntry(AccountsController):
self.reference_totals[d.reference_name] = 0.0
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
self.reference_types[d.reference_name] = d.reference_type
if d.party_type and d.party:
self.reference_parties[d.reference_name] = [d.party_type, d.party]
self.reference_accounts[d.reference_name] = d.account
against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
[scrub(dt) for dt in field_dict.get(d.reference_type)])
@@ -196,7 +196,7 @@ class JournalEntry(AccountsController):
"""Validate totals, stopped and docstatus for orders"""
for reference_name, total in self.reference_totals.iteritems():
reference_type = self.reference_types[reference_name]
party_type, party = self.reference_parties.get(reference_name)
account = self.reference_accounts[reference_name]
if reference_type in ("Sales Order", "Purchase Order"):
order = frappe.db.get_value(reference_type, reference_name,
@@ -212,8 +212,8 @@ class JournalEntry(AccountsController):
if cstr(order.status) == "Stopped":
frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name))
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency")
if party_account_currency == self.company_currency:
account_currency = get_account_currency(account)
if account_currency == self.company_currency:
voucher_total = order.base_grand_total
else:
voucher_total = order.grand_total
@@ -476,9 +476,18 @@ class JournalEntry(AccountsController):
frappe.throw("Accounts table cannot be blank.")
def set_account_and_party_balance(self):
account_balance = {}
party_balance = {}
for d in self.get("accounts"):
d.account_balance = get_balance_on(account=d.account, date=self.posting_date)
d.party_balance = get_balance_on(party_type=d.party_type, party=d.party, date=self.posting_date)
if d.account not in account_balance:
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
if (d.party_type, d.party) not in party_balance:
party_balance[(d.party_type, d.party)] = get_balance_on(party_type=d.party_type,
party=d.party, date=self.posting_date)
d.account_balance = account_balance[d.account]
d.party_balance = party_balance[(d.party_type, d.party)]
@frappe.whitelist()
def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None):
@@ -600,8 +609,8 @@ def get_payment_entry_from_sales_order(sales_order):
jv = get_payment_entry(so)
jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name)
party_account = get_party_account(so.company, so.customer, "Customer")
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
party_account = get_party_account("Customer", so.customer, so.company)
party_account_currency = get_account_currency(party_account)
exchange_rate = get_exchange_rate(party_account, party_account_currency, so.company)
@@ -651,8 +660,8 @@ def get_payment_entry_from_purchase_order(purchase_order):
jv = get_payment_entry(po)
jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name)
party_account = get_party_account(po.company, po.supplier, "Supplier")
party_account_currency = frappe.db.get_value("Account", party_account, "account_currency")
party_account = get_party_account("Supplier", po.supplier, po.company)
party_account_currency = get_account_currency(party_account)
exchange_rate = get_exchange_rate(party_account, party_account_currency, po.company)
@@ -770,7 +779,7 @@ def get_party_account_and_balance(company, party_type, party):
frappe.msgprint(_("No Permission"), raise_exception=1)
from erpnext.accounts.party import get_party_account
account = get_party_account(company, party, party_type)
account = get_party_account(party_type, party, company)
account_balance = get_balance_on(account=account)
party_balance = get_balance_on(party_type=party_type, party=party)
@@ -817,17 +826,20 @@ def get_exchange_rate(account, account_currency, company,
if account_currency != company_currency:
if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name:
exchange_rate = frappe.db.get_value(reference_type, reference_name, "conversion_rate")
elif account_details.account_type == "Bank" and \
elif account_details and account_details.account_type == "Bank" and \
((account_details.root_type == "Asset" and flt(credit) > 0) or
(account_details.root_type == "Liability" and debit)):
exchange_rate = get_average_exchange_rate(account)
if not exchange_rate:
if not exchange_rate and account_currency:
exchange_rate = get_exchange_rate(account_currency, company_currency)
else:
exchange_rate = 1
return exchange_rate
# don't return None or 0 as it is multipled with a value and that value could be lost
return exchange_rate or 1
def get_average_exchange_rate(account):
exchange_rate = 0

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

@@ -44,7 +44,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
}
});
},
refresh: function() {
this.frm.disable_save();
},
@@ -77,8 +77,8 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
var invoices = [];
$.each(me.frm.doc.invoices || [], function(i, row) {
if (row.invoice_number && !inList(invoices, row.invoice_number))
invoices.push(row.invoice_number);
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",

View File

@@ -130,7 +130,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Column Break",
"label": "",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
@@ -362,7 +362,7 @@
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"modified": "2015-02-05 05:11:42.105088",
"modified": "2015-09-21 03:41:24.672227",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@@ -79,8 +79,12 @@ class PaymentReconciliation(Document):
`tabGL Entry`
where
party_type = %(party_type)s and party = %(party)s
and voucher_type != "Journal Entry"
and account = %(account)s and {dr_or_cr} > 0 {cond}
and (CASE
WHEN voucher_type = 'Journal Entry'
THEN ifnull(against_voucher, '') = ''
ELSE 1=1
END)
group by voucher_type, voucher_no
""".format(**{
"cond": cond,
@@ -136,12 +140,16 @@ class PaymentReconciliation(Document):
ent.outstanding_amount = e.get('outstanding_amount')
def reconcile(self, args):
for e in self.get('payments'):
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"
lst = []
for e in self.get('payments'):
if e.invoice_type and e.invoice_number and e.allocated_amount:
if e.invoice_number and e.allocated_amount:
lst.append({
'voucher_no' : e.journal_entry,
'voucher_detail_no' : e.voucher_detail_number,

View File

@@ -124,7 +124,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Column Break",
"label": "",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
@@ -135,51 +135,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Allocated amount",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "Sales Invoice",
"fieldname": "invoice_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Invoice Type",
"no_copy": 0,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -202,6 +157,28 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Allocated amount",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -243,27 +220,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "col_break2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Column Break",
"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
}
],
"hide_heading": 0,
@@ -273,7 +229,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2014-12-25 16:26:48.345281",
"modified": "2015-09-21 03:39:40.320070",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Payment",

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":
@@ -71,7 +72,7 @@ def get_outstanding_vouchers(args):
# Get all outstanding sales /purchase invoices
outstanding_invoices = get_outstanding_invoices(amount_query, args.get("party_account"),
args.get("party_type"), args.get("party"), with_journal_entry=False)
args.get("party_type"), args.get("party"))
# Get all SO / PO which are not fully billed or aginst which full advance not paid
orders_to_be_billed = get_orders_to_be_billed(args.get("party_type"), args.get("party"),
@@ -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"

View File

@@ -2375,4 +2375,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title"
}
}

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

@@ -504,7 +504,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-19 12:46:32.687299",
"modified": "2015-08-28 02:57:08.769473",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",

View File

@@ -36,6 +36,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
refresh: function(doc, dt, dn) {
this._super();
if(cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
cur_frm.msgbox.hide();
}
cur_frm.dashboard.reset();
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
@@ -146,7 +151,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
method: "set_missing_values",
callback: function(r) {
if(!r.exc) {
cur_frm.pos_print_format = r.message.print_format;
if(r.message && r.message.print_format) {
cur_frm.pos_print_format = r.message.print_format;
}
cur_frm.doc.__missing_values_set = true;
me.frm.script_manager.trigger("update_stock");
frappe.model.set_default_values(me.frm.doc);
@@ -175,7 +182,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.apply_pricing_rule();
})
},
debit_to: function() {
var me = this;
if(this.frm.doc.debit_to) {
@@ -193,7 +200,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}
}
});
}
}
},
allocated_amount: function() {
@@ -417,9 +424,9 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
})
if(cur_frm.doc.is_pos) {
frappe.msgprint('<a class="btn btn-primary" \
cur_frm.msgbox = frappe.msgprint('<a class="btn btn-primary" \
onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">Print</a>\
<a class="btn btn-default" href="#Form/Sales Invoice/New">New</a>');
<a class="btn btn-default" href="#Form/Sales Invoice/New Sales Invoice">New</a>');
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
cur_frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);

File diff suppressed because it is too large Load Diff

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"
@@ -35,6 +36,15 @@ class SalesInvoice(SellingController):
'overflow_type': 'billing'
}]
def set_indicator(self):
"""Set indicator for portal"""
if self.outstanding_amount > 0:
self.indicator_color = "orange"
self.indicator_title = _("Unpaid")
else:
self.indicator_color = "green"
self.indicator_title = _("Paid")
def validate(self):
super(SalesInvoice, self).validate()
self.validate_posting_time()
@@ -90,7 +100,7 @@ class SalesInvoice(SellingController):
# this sequence because outstanding may get -ve
self.make_gl_entries()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
@@ -161,10 +171,10 @@ class SalesInvoice(SellingController):
'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)"""
}
])
def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit
validate_against_credit_limit = False
for d in self.get("items"):
if not (d.sales_order or d.delivery_note):
@@ -177,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)
@@ -282,7 +292,7 @@ class SalesInvoice(SellingController):
reconcile_against_document(lst)
def validate_debit_to_acc(self):
account = frappe.db.get_value("Account", self.debit_to,
account = frappe.db.get_value("Account", self.debit_to,
["account_type", "report_type", "account_currency"], as_dict=True)
if account.report_type != "Balance Sheet":
@@ -290,7 +300,7 @@ class SalesInvoice(SellingController):
if self.customer and account.account_type != "Receivable":
frappe.throw(_("Debit To account must be a Receivable account"))
self.party_account_currency = account.account_currency
def validate_fixed_asset_account(self):
@@ -437,18 +447,18 @@ class SalesInvoice(SellingController):
if cint(self.is_pos) == 1:
if flt(self.paid_amount) == 0:
if self.cash_bank_account:
frappe.db.set(self, 'paid_amount',
flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount")))
frappe.db.set(self, 'paid_amount',
flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount")))
else:
# show message that the amount is not paid
frappe.db.set(self,'paid_amount',0)
frappe.msgprint(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
else:
frappe.db.set(self,'paid_amount',0)
frappe.db.set(self, 'base_paid_amount',
frappe.db.set(self, 'base_paid_amount',
flt(self.paid_amount*self.conversion_rate, self.precision("base_paid_amount")))
def check_prev_docstatus(self):
for d in self.get('items'):
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
@@ -487,7 +497,7 @@ class SalesInvoice(SellingController):
from erpnext.accounts.general_ledger import merge_similar_entries
gl_entries = []
self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
@@ -522,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,
@@ -538,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,
@@ -557,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({
@@ -585,8 +595,8 @@ 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({
"account": self.debit_to,

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

@@ -456,7 +456,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-19 12:46:33.165519",
"modified": "2015-08-28 02:57:00.766305",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",

View File

@@ -5,6 +5,3 @@ cur_frm.cscript.tax_table = "Sales Taxes and Charges";
{% include "public/js/controllers/accounts.js" %}
frappe.ui.form.on("Sales Taxes and Charges Template", "onload", function(frm) {
erpnext.add_applicable_territory();
});

View File

@@ -164,29 +164,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Specify a list of Territories, for which, this Taxes Master is valid",
"fieldname": "territories",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Territories",
"no_copy": 0,
"options": "Applicable Territory",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@@ -198,7 +175,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:19:46.488710",
"modified": "2015-09-17 07:09:28.797959",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
@@ -206,7 +183,7 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,

View File

@@ -5,7 +5,6 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
from frappe.utils.nestedset import get_root_of
class SalesTaxesandChargesTemplate(Document):
def validate(self):
@@ -20,10 +19,6 @@ def valdiate_taxes_and_charges_template(doc):
where ifnull(is_default,0) = 1 and name != %s and company = %s""".format(doc.doctype),
(doc.name, doc.company))
if doc.meta.get_field("territories"):
if not doc.territories:
doc.append("territories", {"territory": get_root_of("Territory") })
for tax in doc.get("taxes"):
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, doc)

View File

@@ -20,19 +20,7 @@
"rate": 6.36
}
],
"title": "_Test Sales Taxes and Charges Template",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "All Territories"
},
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"title": "_Test Sales Taxes and Charges Template"
},
{
"company": "_Test Company",
@@ -115,14 +103,7 @@
"row_id": 7
}
],
"title": "_Test India Tax Master",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory India"
}
]
"title": "_Test India Tax Master"
},
{
"company": "_Test Company",
@@ -145,13 +126,76 @@
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template - Rest of the World",
"territories": [
"title": "_Test Sales Taxes and Charges Template - Rest of the World"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 4
}
]
],
"title": "_Test Sales Taxes and Charges Template 1"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template 2"
},
{
"doctype" : "Sales Taxes and Charges Template",
"title": "_Test Tax 1",
"company": "_Test Company",
"taxes":[{
"charge_type": "Actual",
"account_head": "Sales Expenses - _TC",
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 1000
}]
},
{
"doctype" : "Sales Taxes and Charges Template",
"title": "_Test Tax 2",
"company": "_Test Company",
"taxes":[{
"charge_type": "Actual",
"account_head": "Sales Expenses - _TC",
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 200
}]
}
]

View File

@@ -1,8 +1,3 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
$.extend(cur_frm.cscript, {
onload: function() {
erpnext.add_applicable_territory();
}
});

View File

@@ -1,388 +1,397 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:label",
"creation": "2013-06-25 11:48:03",
"custom": 0,
"description": "Specify conditions to calculate shipping amount",
"docstatus": 0,
"doctype": "DocType",
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:label",
"creation": "2013-06-25 11:48:03",
"custom": 0,
"description": "Specify conditions to calculate shipping amount",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "example: Next Day Shipping",
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Shipping Rule Label",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "example: Next Day Shipping",
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Shipping Rule Label",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disabled",
"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,
"default": "Net Total",
"fieldname": "calculate_based_on",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Calculate Based On",
"no_copy": 0,
"options": "Net Total\nNet Weight",
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "Net Total",
"fieldname": "calculate_based_on",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Calculate Based On",
"no_copy": 0,
"options": "Net Total\nNet Weight",
"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": "rule_conditions_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Rule Conditions",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "rule_conditions_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Rule Conditions",
"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": "conditions",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Rule Conditions",
"no_copy": 0,
"options": "Shipping Rule Condition",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "conditions",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Rule Conditions",
"no_copy": 0,
"options": "Shipping Rule Condition",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Countries",
"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": "Specify a list of Territories, for which, this Shipping Rule is valid",
"fieldname": "territories",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid For Territories",
"no_copy": 0,
"options": "Applicable Territory",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "worldwide_shipping",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Worldwide Shipping",
"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,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.worldwide_shipping",
"fieldname": "countries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Countries",
"no_copy": 0,
"options": "Shipping Rule Country",
"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,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Company",
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Company",
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Account",
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Account",
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Cost Center",
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Cost Center",
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-truck",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shipping Rule",
"owner": "Administrator",
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-truck",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-22 08:30:57.226342",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shipping Rule",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"amend": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"amend": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
],
"read_only": 0,
"read_only_onload": 0
}
}

View File

@@ -22,6 +22,12 @@ class ShippingRule(Document):
self.sort_shipping_rule_conditions()
self.validate_overlapping_shipping_rule_conditions()
if self.worldwide_shipping:
self.countries = []
elif not len([d.country for d in self.countries if d.country]):
frappe.throw(_("Please specify a country for this Shipping Rule or check Worldwide Shipping"))
def validate_from_to_values(self):
zero_to_values = []

View File

@@ -1,116 +1,100 @@
[
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule",
"name": "_Test Shipping Rule",
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule",
"name": "_Test Shipping Rule",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"to_value": 100
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"to_value": 200
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 0.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory"
}
]
},
],
"worldwide_shipping": 1
},
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - India",
"name": "_Test Shipping Rule - India",
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - India",
"name": "_Test Shipping Rule - India",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"to_value": 100
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"to_value": 200
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 0.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory India"
}
],
"countries": [
{"country": "India"}
]
},
},
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - Rest of the World",
"name": "_Test Shipping Rule - Rest of the World",
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - Rest of the World",
"name": "_Test Shipping Rule - Rest of the World",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 500.0,
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 500.0,
"to_value": 1000
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 1001,
"parentfield": "conditions",
"shipping_amount": 1000.0,
"doctype": "Shipping Rule Condition",
"from_value": 1001,
"parentfield": "conditions",
"shipping_amount": 1000.0,
"to_value": 2000
},
},
{
"doctype": "Shipping Rule Condition",
"from_value": 2001,
"parentfield": "conditions",
"doctype": "Shipping Rule Condition",
"from_value": 2001,
"parentfield": "conditions",
"shipping_amount": 1500.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
],
"worldwide_shipping": 1
}
]
]

View File

@@ -2,26 +2,27 @@
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-20 12:48:38",
"creation": "2015-09-17 06:43:22.767534",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "",
"fieldname": "territory",
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Territory",
"label": "Country",
"no_copy": 0,
"options": "Territory",
"options": "Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -33,18 +34,20 @@
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-01-01 14:29:58.724652",
"modified": "2015-09-17 06:43:22.767534",
"modified_by": "Administrator",
"module": "Setup",
"name": "Applicable Territory",
"module": "Accounts",
"name": "Shipping Rule Country",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"read_only": 0,
"read_only_onload": 0
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -1,12 +1,10 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ApplicableTerritory(Document):
pass
class ShippingRuleCountry(Document):
pass

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.add_fetch("customer", "customer_group", "customer_group" );
cur_frm.add_fetch("supplier", "supplier_type", "supplier_type" );
cur_frm.toggle_reqd("sales_tax_template", cur_frm.doc.tax_type=="Sales");
cur_frm.toggle_reqd("purchase_tax_template", cur_frm.doc.tax_type=="Purchase");
frappe.ui.form.on("Tax Rule", "onload", function(frm) {
if(frm.doc.__islocal){
frm.set_value("use_for_shopping_cart", 1);
}
})
frappe.ui.form.on("Tax Rule", "use_for_shopping_cart", function(frm) {
if(!frm.doc.use_for_shopping_cart && (frappe.get_list("Tax Rule", {"use_for_shopping_cart":1}).length == 0)){
frappe.model.get_value("Shopping Cart Settings", "Shopping Cart Settings", "enabled", function(docfield) {
if(docfield.enabled){
frm.set_value("use_for_shopping_cart", 1);
frappe.throw(__("Shopping Cart is enabled"));
}
});
}
})
frappe.ui.form.on("Tax Rule", "customer", function(frm) {
frappe.call({
method:"erpnext.accounts.doctype.tax_rule.tax_rule.get_party_details",
args: {
"party": frm.doc.customer,
"party_type": "customer"
},
callback: function(r) {
if(!r.exc) {
$.each(r.message, function(k, v) {
frm.set_value(k, v);
});
}
}
});
});
frappe.ui.form.on("Tax Rule", "supplier", function(frm) {
frappe.call({
method:"erpnext.accounts.doctype.tax_rule.tax_rule.get_party_details",
args: {
"party": frm.doc.supplier,
"party_type": "supplier"
},
callback: function(r) {
if(!r.exc) {
$.each(r.message, function(k, v) {
frm.set_value(k, v);
});
}
}
});
});

View File

@@ -1,27 +1,30 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_import": 1,
"allow_rename": 0,
"creation": "2013-04-30 12:58:38",
"autoname": "TR.####",
"creation": "2015-08-07 02:33:52.670866",
"custom": 0,
"description": "System for managing Backups",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"document_type": "Setup",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "setup",
"fieldtype": "Section Break",
"default": "Sales",
"fieldname": "tax_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Download Backups",
"in_list_view": 1,
"label": "Tax Type",
"no_copy": 0,
"options": "Sales\nPurchase",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -34,13 +37,13 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "current_backups",
"fieldtype": "HTML",
"fieldname": "use_for_shopping_cart",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Current Backups",
"label": "Use for Shopping Cart",
"no_copy": 0,
"permlevel": 0,
"precision": "",
@@ -56,56 +59,12 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "",
"fieldname": "sync_with_dropbox",
"fieldtype": "Section Break",
"fieldname": "column_break_1",
"fieldtype": "Column 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": "",
@@ -121,18 +80,18 @@
"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",
"depends_on": "eval:doc.tax_type==\"Sales\"",
"fieldname": "sales_tax_template",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Upload Backups to Dropbox",
"label": "Sales Tax Template",
"no_copy": 0,
"options": "Never\nWeekly\nDaily",
"options": "Sales Taxes and Charges Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -145,17 +104,18 @@
"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",
"depends_on": "eval:doc.tax_type==\"Purchase\"",
"fieldname": "purchase_tax_template",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Send Notifications To",
"label": "Purchase Tax Template",
"no_copy": 0,
"options": "Purchase Taxes and Charges Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -168,123 +128,16 @@
"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",
"fieldname": "filters",
"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",
"label": "Filters",
"no_copy": 0,
"options": "Never\nDaily\nWeekly",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -297,15 +150,18 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allow_gdrive_access",
"fieldtype": "Button",
"depends_on": "eval:doc.tax_type==\"Sales\"",
"fieldname": "customer",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allow Google Drive Access",
"label": "Customer",
"no_copy": 0,
"options": "Customer",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -318,15 +174,40 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "verification_code",
"depends_on": "eval:doc.tax_type==\"Purchase\"",
"fieldname": "supplier",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Supplier",
"no_copy": 0,
"options": "Supplier",
"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,
"fieldname": "billing_city",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Enter Verification Code",
"label": "Billing City",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -339,15 +220,16 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "validate_gdrive",
"fieldtype": "Button",
"fieldname": "billing_state",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Validate",
"label": "Billing State",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@@ -360,17 +242,19 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "gdrive_access_allowed",
"fieldtype": "Check",
"hidden": 1,
"fieldname": "billing_country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Google Drive Access Allowed",
"label": "Billing Country",
"no_copy": 0,
"options": "Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -381,17 +265,17 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "gdrive_credentials",
"fieldtype": "Text",
"hidden": 1,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credentials",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -402,17 +286,66 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "database_folder_id",
"depends_on": "eval:doc.tax_type==\"Sales\"",
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Customer Group",
"no_copy": 0,
"options": "Customer Group",
"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": "eval:doc.tax_type==\"Purchase\"",
"fieldname": "supplier_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Supplier Type",
"no_copy": 0,
"options": "Supplier Type",
"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,
"fieldname": "shipping_city",
"fieldtype": "Data",
"hidden": 1,
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Database Folder ID",
"label": "Shipping City",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -423,17 +356,216 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "files_folder_id",
"fieldname": "shipping_state",
"fieldtype": "Data",
"hidden": 1,
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Files Folder ID",
"label": "Shipping State",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"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": "shipping_country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Country",
"no_copy": 0,
"options": "Country",
"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,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Validity",
"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,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "From Date",
"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,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "To Date",
"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,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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,
"default": "1",
"fieldname": "priority",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Priority",
"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,
"fieldname": "column_break_20",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Company",
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -443,17 +575,16 @@
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-cloud-upload",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-05-26 04:54:10.193573",
"modified": "2015-09-24 07:59:06.502058",
"modified_by": "Administrator",
"module": "Setup",
"name": "Backup Manager",
"module": "Accounts",
"name": "Tax Rule",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
@@ -461,16 +592,16 @@
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"delete": 1,
"email": 1,
"export": 0,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
@@ -478,5 +609,7 @@
}
],
"read_only": 0,
"read_only_onload": 0
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, 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.model.document import Document
from frappe.utils import cstr
class IncorrectCustomerGroup(frappe.ValidationError): pass
class IncorrectSupplierType(frappe.ValidationError): pass
class ConflictingTaxRule(frappe.ValidationError): pass
class TaxRule(Document):
def __setup__(self):
self.flags.ignore_these_exceptions_in_test = [ConflictingTaxRule]
def validate(self):
self.validate_tax_template()
self.validate_date()
self.validate_filters()
def validate_tax_template(self):
if self.tax_type== "Sales":
self.purchase_tax_template = self.supplier = self.supplier_type= None
if self.customer:
self.customer_group = None
else:
self.sales_tax_template= self.customer = self.customer_group= None
if self.supplier:
self.supplier_type = None
if not (self.sales_tax_template or self.purchase_tax_template):
frappe.throw(_("Tax Template is mandatory."))
def validate_date(self):
if self.from_date and self.to_date and self.from_date > self.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
def validate_filters(self):
filters = {
"tax_type": self.tax_type,
"customer": self.customer,
"customer_group": self.customer_group,
"supplier": self.supplier,
"supplier_type": self.supplier_type,
"billing_city": self.billing_city,
"billing_state": self.billing_state,
"billing_country": self.billing_country,
"shipping_city": self.shipping_city,
"shipping_state": self.shipping_state,
"shipping_country": self.shipping_country,
"company": self.company
}
conds=""
for d in filters:
if conds:
conds += " and "
conds += """ifnull({0}, '') = '{1}'""".format(d, frappe.db.escape(cstr(filters[d])))
if self.from_date and self.to_date:
conds += """ and ((from_date > '{from_date}' and from_date < '{to_date}') or
(to_date > '{from_date}' and to_date < '{to_date}') or
('{from_date}' > from_date and '{from_date}' < to_date) or
('{from_date}' = from_date and '{to_date}' = to_date))""".format(from_date=self.from_date, to_date=self.to_date)
elif self.from_date and not self.to_date:
conds += """ and to_date > '{from_date}'""".format(from_date = self.from_date)
elif self.to_date and not self.from_date:
conds += """ and from_date < '{to_date}'""".format(to_date = self.to_date)
tax_rule = frappe.db.sql("select name, priority \
from `tabTax Rule` where {0} and name != '{1}'".format(conds, self.name), as_dict=1)
if tax_rule:
if tax_rule[0].priority == self.priority:
frappe.throw(_("Tax Rule Conflicts with {0}".format(tax_rule[0].name)), ConflictingTaxRule)
@frappe.whitelist()
def get_party_details(party, party_type, args=None):
out = {}
if args:
billing_filters= {"name": args.get("billing_address")}
shipping_filters= {"name": args.get("shipping_address")}
else:
billing_filters= {party_type: party, "is_primary_address": 1}
shipping_filters= {party_type:party, "is_shipping_address": 1}
billing_address= frappe.get_all("Address", fields=["city", "state", "country"], filters= billing_filters)
shipping_address= frappe.get_all("Address", fields=["city", "state", "country"], filters= shipping_filters)
if billing_address:
out["billing_city"]= billing_address[0].city
out["billing_state"]= billing_address[0].state
out["billing_country"]= billing_address[0].country
if shipping_address:
out["shipping_city"]= shipping_address[0].city
out["shipping_state"]= shipping_address[0].state
out["shipping_country"]= shipping_address[0].country
return out
def get_tax_template(posting_date, args):
"""Get matching tax rule"""
args = frappe._dict(args)
conditions = []
for key, value in args.iteritems():
if key in "use_for_shopping_cart":
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
else:
conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value))))
matching = frappe.db.sql("""select * from `tabTax Rule`
where {0}""".format(" and ".join(conditions)), as_dict = True)
if not matching:
return None
for rule in matching:
rule.no_of_keys_matched = 0
for key in args:
if rule.get(key): rule.no_of_keys_matched += 1
rule = sorted(matching, lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority))[0]
return rule.sales_tax_template or rule.purchase_tax_template

View File

@@ -0,0 +1,28 @@
[
{
"doctype": "Tax Rule",
"tax_type" : "Sales",
"sales_tax_template": "_Test Tax 1",
"use_for_shopping_cart": 1,
"billing_city": "_Test City",
"billing_state": "Test State",
"billing_country": "India",
"shipping_city": "_Test City",
"shipping_country": "India",
"priority": 1,
"company": "_Test Company"
},
{
"doctype": "Tax Rule",
"tax_type" : "Sales",
"sales_tax_template": "_Test Tax 2",
"use_for_shopping_cart": 0,
"billing_city": "_Test City",
"billing_country": "India",
"shipping_city": "_Test City",
"shipping_state": "Test State",
"shipping_country": "India",
"priority": 2,
"company": "_Test Company"
}
]

View File

@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from erpnext.accounts.doctype.tax_rule.tax_rule import IncorrectCustomerGroup, IncorrectSupplierType, ConflictingTaxRule, get_tax_template
test_records = frappe.get_test_records('Tax Rule')
class TestTaxRule(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabTax Rule` where use_for_shopping_cart <> 1")
def test_conflict(self):
tax_rule1 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1)
tax_rule1.save()
tax_rule2 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1)
self.assertRaises(ConflictingTaxRule, tax_rule2.save)
def test_conflict_with_non_overlapping_dates(self):
tax_rule1 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01")
tax_rule1.save()
tax_rule2 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, to_date = "2013-01-01")
tax_rule2.save()
self.assertTrue(tax_rule2.name)
def test_conflict_with_overlapping_dates(self):
tax_rule1 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01", to_date = "2015-01-05")
tax_rule1.save()
tax_rule2 = make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-03", to_date = "2015-01-09")
self.assertRaises(ConflictingTaxRule, tax_rule2.save)
def test_tax_template(self):
tax_rule = make_tax_rule()
self.assertEquals(tax_rule.purchase_tax_template, None)
def test_select_tax_rule_based_on_customer(self):
make_tax_rule(customer= "_Test Customer",
sales_tax_template = "_Test Sales Taxes and Charges Template", save=1)
make_tax_rule(customer= "_Test Customer 1",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", save=1)
make_tax_rule(customer= "_Test Customer 2",
sales_tax_template = "_Test Sales Taxes and Charges Template 2", save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer 2"}),
"_Test Sales Taxes and Charges Template 2")
def test_select_tax_rule_based_on_better_match(self):
make_tax_rule(customer= "_Test Customer", billing_city = "Test City", billing_state = "Test State",
sales_tax_template = "_Test Sales Taxes and Charges Template", save=1)
make_tax_rule(customer= "_Test Customer", billing_city = "Test City1", billing_state = "Test State",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City", "billing_state": "Test State"}),
"_Test Sales Taxes and Charges Template")
def test_select_tax_rule_based_on_state_match(self):
make_tax_rule(customer= "_Test Customer", shipping_state = "Test State",
sales_tax_template = "_Test Sales Taxes and Charges Template", save=1)
make_tax_rule(customer= "_Test Customer", shipping_state = "Test State12",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", priority=2, save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer", "shipping_state": "Test State"}),
"_Test Sales Taxes and Charges Template")
def test_select_tax_rule_based_on_better_priority(self):
make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
sales_tax_template = "_Test Sales Taxes and Charges Template", priority=1, save=1)
make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", priority=2, save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City"}),
"_Test Sales Taxes and Charges Template 1")
def test_select_tax_rule_based_cross_matching_keys(self):
make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
sales_tax_template = "_Test Sales Taxes and Charges Template", save=1)
make_tax_rule(customer= "_Test Customer 1", billing_city = "Test City 1",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
None)
def test_select_tax_rule_based_cross_partially_keys(self):
make_tax_rule(customer= "_Test Customer", billing_city = "Test City",
sales_tax_template = "_Test Sales Taxes and Charges Template", save=1)
make_tax_rule(billing_city = "Test City 1",
sales_tax_template = "_Test Sales Taxes and Charges Template 1", save=1)
self.assertEquals(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
"_Test Sales Taxes and Charges Template 1")
def make_tax_rule(**args):
args = frappe._dict(args)
tax_rule = frappe.new_doc("Tax Rule")
for key, val in args.iteritems():
if key != "save":
tax_rule.set(key, val)
tax_rule.company = args.company or "_Test Company"
if args.save:
tax_rule.insert()
return tax_rule

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,
@@ -20,7 +20,7 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
if not party:
return {}
if not frappe.db.exists(party_type, party):
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
@@ -29,7 +29,7 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
def _get_party_details(party=None, account=None, party_type="Customer", company=None,
posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, doctype))
party = out[party_type.lower()]
@@ -43,6 +43,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
set_contact_details(out, party, party_type)
set_other_values(out, party, party_type)
set_price_list(out, party, party_type, price_list)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_type)
if not out.get("currency"):
out["currency"] = currency
@@ -50,9 +51,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
# sales team
if party_type=="Customer":
out["sales_team"] = [{
"sales_person": d.sales_person,
"sales_designation": d.sales_designation,
"allocated_percentage": d.allocated_percentage
"sales_person": d.sales_person
} for d in party.get("sales_team")]
return out
@@ -99,11 +98,24 @@ def set_other_values(out, party, party_type):
out[f] = party.get(f)
# fields prepended with default in Customer doctype
for f in ['currency', 'taxes_and_charges'] \
for f in ['currency'] \
+ (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
if party.get("default_" + f):
out[f] = party.get("default_" + f)
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
if party.default_price_list:
return party.default_price_list
if party.doctype == "Customer":
price_list = frappe.db.get_value("Customer Group",
party.customer_group, "default_price_list")
if price_list:
return price_list
return None
def set_price_list(out, party, party_type, given_price_list):
# price list
price_list = filter(None, get_user_permissions().get("Price List", []))
@@ -111,11 +123,7 @@ def set_price_list(out, party, party_type, given_price_list):
price_list = price_list[0] if len(price_list)==1 else None
if not price_list:
price_list = party.default_price_list
if not price_list and party_type=="Customer":
price_list = frappe.db.get_value("Customer Group",
party.customer_group, "default_price_list")
price_list = get_default_price_list(party)
if not price_list:
price_list = given_price_list
@@ -134,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"
@@ -144,69 +152,22 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
"due_date": get_due_date(posting_date, party_type, party, company)
}
return out
def validate_accounting_currency(party):
company_currency = get_company_currency()
# set party account currency
if not party.party_account_currency:
if party.default_currency:
party.party_account_currency = party.default_currency
elif len(set(company_currency.values())) == 1:
party.party_account_currency = company_currency.values()[0]
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"]):
company_currency.setdefault(d.name, d.default_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",
@@ -224,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"""
@@ -240,7 +237,7 @@ def get_due_date(posting_date, party_type, party, company):
credit_days = get_credit_days(party_type, party, company)
if credit_days:
due_date = add_days(posting_date, credit_days)
return due_date
def get_credit_days(party_type, party, company):
@@ -248,30 +245,60 @@ def get_credit_days(party_type, party, company):
if party_type == "Customer":
credit_days_based_on, credit_days, customer_group = \
frappe.db.get_value(party_type, party, ["credit_days_based_on", "credit_days", "customer_group"])
if not credit_days_based_on:
credit_days_based_on, credit_days = \
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"]) \
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
return credit_days_based_on, credit_days
else:
credit_days, supplier_type = frappe.db.get_value(party_type, party, ["credit_days", "supplier_type"])
if not credit_days:
credit_days = frappe.db.get_value("Supplier Type", supplier_type, "credit_days") \
or frappe.db.get_value("Company", company, "credit_days")
return credit_days
def validate_due_date(posting_date, due_date, party_type, party, company):
if getdate(due_date) < getdate(posting_date):
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:
msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)")
.format(date_diff(due_date, default_due_date)))
else:
frappe.throw(_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date)))
frappe.throw(_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date)))
@frappe.whitelist()
def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_type=None,
billing_address=None, shipping_address=None, use_for_shopping_cart=None):
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type.lower(): party,
"customer_group": customer_group,
"supplier_type": supplier_type,
"company": company
}
if billing_address or shipping_address:
args.update(get_party_details(party, party_type, {"billing_address": billing_address, \
"shipping_address": shipping_address }))
else:
args.update(get_party_details(party, party_type))
if party_type=="Customer":
args.update({"tax_type": "Sales"})
else:
args.update({"tax_type": "Purchase"})
if use_for_shopping_cart:
args.update({"use_for_shopping_cart": use_for_shopping_cart})
return get_tax_template(posting_date, args)

View File

@@ -209,7 +209,8 @@ class ReceivablePayableReport(object):
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
voucher_type, voucher_no, against_voucher_type, against_voucher, account_currency, remarks, {0}
from `tabGL Entry`
where docstatus < 2 and party_type=%s {1} order by posting_date, party"""
where docstatus < 2 and party_type=%s and ifnull(party, '') != '' {1}
order by posting_date, party"""
.format(select_fields, conditions), values, as_dict=True)
return self.gl_entries

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,52 @@ 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,
acc_dict.opening_in_account_currency))
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
if filters.get("group_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"):
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), {}]
# Totals and closing for individual ledger, if grouped by account
if filters.get("group_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:
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
opening_debit = opening_credit = 0.0
for gl in gl_entries:
if gl.posting_date < from_date:
opening_debit += flt(gl.debit, 3)
opening_credit += flt(gl.credit, 3)
else:
data.append(gl)
if not (filters.get("account") or filters.get("party")):
data = [{
"account": "'" + _("Opening") + "'",
"debit": opening_debit,
"credit": opening_credit
}] + data
# 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 +211,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 +236,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 +278,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
@@ -397,7 +400,7 @@ def get_stock_rbnb_difference(posting_date, company):
# Amount should be credited
return flt(stock_rbnb) + flt(sys_bal)
def get_outstanding_invoices(amount_query, account, party_type, party, with_journal_entry=True):
def get_outstanding_invoices(amount_query, account, party_type, party):
all_outstanding_vouchers = []
outstanding_voucher_list = frappe.db.sql("""
select
@@ -407,6 +410,11 @@ def get_outstanding_invoices(amount_query, account, party_type, party, with_jour
`tabGL Entry`
where
account = %s and party_type=%s and party=%s and {amount_query} > 0
and (CASE
WHEN voucher_type = 'Journal Entry'
THEN ifnull(against_voucher, '') = ''
ELSE 1=1
END)
group by voucher_type, voucher_no
""".format(amount_query = amount_query), (account, party_type, party), as_dict = True)
@@ -425,9 +433,6 @@ def get_outstanding_invoices(amount_query, account, party_type, party, with_jour
payment_amount = -1*payment_amount[0][0] if payment_amount else 0
precision = frappe.get_precision("Sales Invoice", "outstanding_amount")
if not with_journal_entry and d.voucher_type=="Journal Entry":
continue
if d.invoice_amount > payment_amount:
all_outstanding_vouchers.append({

View File

@@ -4,17 +4,17 @@ frappe.listview_settings['Purchase Order'] = {
get_indicator: function(doc) {
if(doc.status==="Stopped") {
return [__("Stopped"), "darkgrey", "status,=,Stopped"];
} else if(flt(doc.per_received) < 100 && doc.status!=="Stopped") {
if(flt(doc.per_billed) < 100) {
} else if(flt(doc.per_received, 2) < 100 && doc.status!=="Stopped") {
if(flt(doc.per_billed, 2) < 100) {
return [__("To Receive and Bill"), "orange",
"per_received,<,100|per_billed,<,100|status,!=,Stopped"];
} else {
return [__("To Receive"), "orange",
"per_received,<,100|per_billed,=,100|status,!=,Stopped"];
}
} else if(flt(doc.per_received) == 100 && flt(doc.per_billed) < 100 && doc.status!=="Stopped") {
} else if(flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) < 100 && doc.status!=="Stopped") {
return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Stopped"];
} else if(flt(doc.per_received) == 100 && flt(doc.per_billed) == 100 && doc.status!=="Stopped") {
} else if(flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) == 100 && doc.status!=="Stopped") {
return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Stopped"];
}
},

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

@@ -0,0 +1,8 @@
- **Tax Rule:** Define rules to automatically select a Tax Template based on Customer, Supplier, Billing Address or Shipping Address
- Changes to **Shopping Cart:**
- The prices will be based on only a single Price List defined in Shopping Cart Settings. Essentially, it means that your Shopping Cart will be available only in a single currency.
- Shipping Rule will be defined per Country, instead of Territory.
- Taxes will be applied based on the new Tax Rule system, instead of Territory.
- **Important Note:** Your Shopping Cart Settings have been disabled. The new changes require you to review your Price List, Tax Rules and Shipping Rule, update the settings, and then enable Shopping Cart again.
- Enhancements in Customer Portal user interface
- Sales Team is now editable after submission of Sales Order, Sales Invoice and Delivery Note

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

@@ -106,6 +106,11 @@ def get_data():
"name": "Accounts Settings",
"description": _("Default settings for accounting transactions.")
},
{
"type": "doctype",
"name": "Tax Rule",
"description": _("Tax Rule for transactions.")
},
{
"type": "doctype",
"name": "Sales Taxes and Charges Template",
@@ -182,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)
@@ -167,6 +166,9 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rule_for") == "Price":
item.set("pricing_list_rate", ret.get("pricing_list_rate"))
if item.price_list_rate:
item.rate = flt(item.price_list_rate *
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
def set_taxes(self):
if not self.meta.get_field("taxes"):
@@ -217,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)
@@ -424,8 +426,7 @@ class AccountsController(TransactionBase):
if self.get("currency"):
party_type, party = self.get_party()
if party_type and party:
party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency") \
or self.company_currency
party_account_currency = get_party_account_currency(party_type, party, self.company)
if party_account_currency != self.company_currency and self.currency != party_account_currency:
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")

View File

@@ -207,10 +207,10 @@ class StatusUpdater(Document):
# update percent complete in the parent table
if args.get('target_parent_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s`
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
set %(target_parent_field)s = round((select sum(if(%(target_ref_field)s >
ifnull(%(target_field)s, 0), %(target_field)s,
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
from `tab%(target_dt)s` where parent="%(name)s") %(set_modified)s
from `tab%(target_dt)s` where parent="%(name)s"), 2) %(set_modified)s
where name='%(name)s'""" % args)
# update field

View File

@@ -21,37 +21,57 @@ def get_list_context(context=None):
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20):
from frappe.templates.pages.list import get_list
user = frappe.session.user
key = None
if not filters: filters = []
filters.append((doctype, "docstatus", "=", 1))
if user != "Guest" and is_website_user():
# find party for this contact
customers, suppliers = get_customers_suppliers(doctype, user)
if customers:
return post_process(get_list(doctype, txt, filters=[(doctype, "customer", "in", customers)],
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=True))
key, parties = "customer", customers
elif suppliers:
return post_process(get_list(doctype, txt, filters=[(doctype, "supplier", "in", suppliers)],
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=True))
key, parties = "supplier", suppliers
filters.append((doctype, key, "in", parties))
if key:
return post_process(doctype, get_list(doctype, txt,
filters=filters, fields = "name",
limit_start=limit_start, limit_page_length=limit_page_length,
ignore_permissions=True,
order_by = "modified desc"))
else:
return []
return post_process(get_list(doctype, txt, filters, limit_start, limit_page_length))
return post_process(doctype, get_list(doctype, txt, filters, limit_start, limit_page_length,
fields="name", order_by = "modified desc"))
def post_process(result):
for r in result:
r.status_percent = 0
r.status_display = []
def post_process(doctype, data):
result = []
for d in data:
doc = frappe.get_doc(doctype, d.name)
if r.get("per_billed"):
r.status_percent += flt(r.per_billed)
r.status_display.append(_("Billed") if r.per_billed==100 else _("{0}% Billed").format(r.per_billed))
doc.status_percent = 0
doc.status_display = []
if r.get("per_delivered"):
r.status_percent += flt(r.per_delivered)
r.status_display.append(_("Delivered") if r.per_delivered==100 else _("{0}% Delivered").format(r.per_delivered))
if doc.get("per_billed"):
doc.status_percent += flt(doc.per_billed)
doc.status_display.append(_("Billed") if doc.per_billed==100 else _("{0}% Billed").format(doc.per_billed))
r.status_display = ", ".join(r.status_display)
if doc.get("per_delivered"):
doc.status_percent += flt(doc.per_delivered)
doc.status_display.append(_("Delivered") if doc.per_delivered==100 else _("{0}% Delivered").format(doc.per_delivered))
if hasattr(doc, "set_indicator"):
doc.set_indicator()
doc.status_display = ", ".join(doc.status_display)
doc.items_preview = ", ".join([d.item_name for d in doc.items])
result.append(doc)
return result

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

@@ -1,4 +1,6 @@
from __future__ import unicode_literals
from frappe import _
app_name = "erpnext"
app_title = "ERPNext"
app_publisher = "Frappe Technologies Pvt. Ltd."
@@ -27,7 +29,7 @@ blogs.
"""
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "6.2.0"
app_version = "6.4.0"
github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com"
@@ -62,11 +64,26 @@ website_context = {
website_route_rules = [
{"from_route": "/orders", "to_route": "Sales Order"},
{"from_route": "/orders/<path:name>", "to_route": "print", "defaults": {"doctype": "Sales Order"}},
{"from_route": "/orders/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Sales Order",
"parents": [{"title": _("Orders"), "name": "orders"}]
}
},
{"from_route": "/invoices", "to_route": "Sales Invoice"},
{"from_route": "/invoices/<path:name>", "to_route": "print", "defaults": {"doctype": "Sales Invoice"}},
{"from_route": "/invoices/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Sales Invoice",
"parents": [{"title": _("Invoices"), "name": "invoices"}]
}
},
{"from_route": "/shipments", "to_route": "Delivery Note"},
{"from_route": "/shipments/<path:name>", "to_route": "print", "defaults": {"doctype": "Delivery Note"}}
{"from_route": "/shipments/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Delivery Notes",
"parents": [{"title": _("Shipments"), "name": "shipments"}]
}
}
]
has_website_permission = {
@@ -76,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"
@@ -111,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

@@ -89,7 +89,7 @@ class Employee(Document):
user.user_image = self.image
try:
frappe.get_doc({
"doctype": "File Data",
"doctype": "File",
"file_name": self.image,
"attached_to_doctype": "User",
"attached_to_name": self.user_id

View File

@@ -147,7 +147,7 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) {
return{
query: "erpnext.controllers.queries.item_query",
filters: [["Item", "name", "!=", doc.item]]
filters: [["Item", "name", "!=", cur_frm.doc.item]]
}
}

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

@@ -2,56 +2,13 @@
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:operation",
"autoname": "Prompt",
"creation": "2014-11-07 16:20:30.683186",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "operation",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Operation",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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,
@@ -61,7 +18,7 @@
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"in_list_view": 1,
"label": "Default Workstation",
"no_copy": 0,
"options": "Workstation",
@@ -127,7 +84,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-14 02:55:58.575636",
"modified": "2015-09-23 07:32:29.550198",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Operation",

View File

@@ -1,7 +1,7 @@
[
{
"doctype": "Operation",
"operation": "_Test Operation 1",
"name": "_Test Operation 1",
"workstation": "_Test Workstation 1"
}
]

View File

@@ -209,3 +209,6 @@ erpnext.patches.v6_0.fix_planned_qty
erpnext.patches.v6_0.multi_currency
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

View File

@@ -9,7 +9,7 @@ def execute():
"Blog Category", "Blog Settings", "Blogger", "Branch", "Brand", "Buying Settings",
"Comment", "Communication", "Company", "Contact Us Settings",
"Country", "Currency", "Currency Exchange", "Deduction Type", "Department",
"Designation", "Earning Type", "Event", "Feed", "File Data", "Fiscal Year",
"Designation", "Earning Type", "Event", "Feed", "File", "Fiscal Year",
"HR Settings", "Industry Type", "Leave Type", "Letter Head",
"Mode of Payment", "Module Def", "Naming Series", "POS Setting", "Print Heading",
"Report", "Role", "Selling Settings", "Stock Settings", "Supplier Type", "UOM"):

View File

@@ -4,5 +4,5 @@
import frappe
def execute():
for dt in ("Stock Ledger Entry", "Communication", "Comment", "DefaultValue", "DocShare", "File Data", "ToDo", "Feed"):
for dt in ("Stock Ledger Entry", "Communication", "Comment", "DefaultValue", "DocShare", "File", "ToDo", "Feed"):
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")

View File

@@ -37,7 +37,7 @@ def fix_files_for_item(files_path, unlinked_files):
frappe.db.set_value(row.doctype, row.name, "image", new_file_url, update_modified=False)
# set it as attachment of this item code
file_data = frappe.get_doc("File Data", unlinked_files[file_url]["file"])
file_data = frappe.get_doc("File", unlinked_files[file_url]["file"])
file_data.attached_to_doctype = "Item"
file_data.attached_to_name = item_code
file_data.save()
@@ -65,20 +65,20 @@ def rename_and_set_content_hash(files_path, unlinked_files, file_url):
# set content hash if missing
file_data_name = unlinked_files[file_url]["file"]
if not frappe.db.get_value("File Data", file_data_name, "content_hash"):
if not frappe.db.get_value("File", file_data_name, "content_hash"):
with open(new_filename, "r") as f:
content_hash = get_content_hash(f.read())
frappe.db.set_value("File Data", file_data_name, "content_hash", content_hash)
frappe.db.set_value("File", file_data_name, "content_hash", content_hash)
def get_unlinked_files(files_path):
# find files that have the same name as a File Data doc
# and the file_name mentioned in that File Data doc doesn't exist
# find files that have the same name as a File doc
# and the file_name mentioned in that File doc doesn't exist
# and it isn't already attached to a doc
unlinked_files = {}
files = os.listdir(files_path)
for file in files:
if not frappe.db.exists("File Data", {"file_name": file}):
file_data = frappe.db.get_value("File Data", {"name": file},
if not frappe.db.exists("File", {"file_name": file}):
file_data = frappe.db.get_value("File", {"name": file},
["file_name", "attached_to_doctype", "attached_to_name"], as_dict=True)
if (file_data

View File

@@ -0,0 +1,29 @@
# 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():
frappe.reload_doc("accounts", "doctype", "tax_rule")
customers = frappe.db.sql("""select name, default_taxes_and_charges from tabCustomer where
ifnull(default_taxes_and_charges, '') != '' """, as_dict=1)
for d in customers:
tr = frappe.new_doc("Tax Rule")
tr.tax_type = "Sales"
tr.customer = d.name
tr.sales_tax_template = d.default_taxes_and_charges
tr.save()
suppliers = frappe.db.sql("""select name, default_taxes_and_charges from tabSupplier where
ifnull(default_taxes_and_charges, '') != '' """, as_dict=1)
for d in suppliers:
tr = frappe.new_doc("Tax Rule")
tr.tax_type = "Purchase"
tr.supplier = d.name
tr.purchase_tax_template = d.default_taxes_and_charges
tr.save()

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

@@ -2,6 +2,11 @@ from __future__ import unicode_literals
import frappe
def execute():
# remove missing lead
for customer in frappe.db.sql_list("""select name from `tabCustomer`
where ifnull(lead_name, '')!='' and not exists (select name from `tabLead` where name=`tabCustomer`.lead_name)"""):
frappe.db.set_value("Customer", customer, "lead_name", None)
# remove missing default taxes
for customer in frappe.db.sql_list("""select name from `tabCustomer`
where ifnull(default_taxes_and_charges, '')!='' and not exists (
@@ -18,8 +23,3 @@ def execute():
c = frappe.get_doc("Supplier", supplier)
c.default_taxes_and_charges = None
c.save()
# remove missing lead
for customer in frappe.db.sql_list("""select name from `tabCustomer`
where ifnull(lead_name, '')!='' and not exists (select name from `tabLead` where name=`tabCustomer`.lead_name)"""):
frappe.db.set_value("Customer", customer, "lead_name", None)

View File

@@ -0,0 +1,23 @@
import frappe
def execute():
frappe.reload_doc("stock", "doctype", "price_list_country")
frappe.reload_doc("accounts", "doctype", "shipping_rule_country")
frappe.reload_doctype("Price List")
frappe.reload_doctype("Shipping Rule")
frappe.reload_doctype("Shopping Cart Settings")
# for price list
countries = frappe.db.sql_list("select name from tabCountry")
for doctype in ("Price List", "Shipping Rule"):
for at in frappe.db.sql("""select name, parent, territory from `tabApplicable Territory` where
parenttype = %s """, doctype, as_dict=True):
if at.territory in countries:
parent = frappe.get_doc(doctype, at.parent)
if not parent.countries:
parent.append("countries", {"country": at.territory})
parent.save()
frappe.delete_doc("DocType", "Applicable Territory")

View File

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

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

@@ -59,9 +59,15 @@ cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
}
cur_frm.fields_dict['sales_order'].get_query = function(doc) {
var filters = {
'project_name': ["in", doc.__islocal ? [""] : [doc.name, ""]]
};
if (doc.customer) {
filters["customer"] = doc.customer;
}
return {
filters:{
'project_name': doc.name
}
filters: filters
}
}

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

@@ -29,6 +29,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid #ebeff2;
}
.product-image.missing-image {
width: 100%;
@@ -38,6 +39,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid #ebeff2;
border: 1px dashed #d1d8dd;
position: relative;
}

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);
@@ -30,7 +30,7 @@ $.extend(shopping_cart, {
args: {
item_code: opts.item_code,
qty: opts.qty,
with_doc: opts.with_doc || 0
with_items: opts.with_items || 0
},
btn: opts.btn,
callback: function(r) {

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

@@ -49,18 +49,6 @@ $.extend(erpnext, {
}
},
add_applicable_territory: function() {
if(cur_frm.doc.__islocal && (cur_frm.doc.territories || []).length===0) {
var default_territory = frappe.defaults.get_user_default("territory");
if(default_territory) {
var territory = frappe.model.add_child(cur_frm.doc, "Applicable Territory",
"territories");
territory.territory = default_territory;
}
}
},
setup_serial_no: function() {
var grid_row = cur_frm.open_grid_row();
if(!grid_row.fields_dict.serial_no ||
@@ -131,8 +119,8 @@ $.extend(erpnext.utils, {
}
);
}
},
},
copy_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname) {
var d = locals[dt][dn];
if(d[fieldname]){

View File

@@ -20,6 +20,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
price_list: frm.doc.buying_price_list
};
}
args.posting_date = frm.doc.transaction_date;
}
if(!args) return;
@@ -42,6 +43,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
erpnext.utils.get_address_display = function(frm, address_field, display_field) {
if(frm.updating_party_details) return;
if(!address_field) {
if(frm.doc.customer) {
address_field = "customer_address";
@@ -49,14 +51,32 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field)
address_field = "supplier_address";
} else return;
}
if(!display_field) display_field = "address_display";
if(frm.doc[address_field]) {
frappe.call({
method: "erpnext.utilities.doctype.address.address.get_address_display",
args: {"address_dict": frm.doc[address_field] },
callback: function(r) {
if(r.message)
if(r.message){
frm.set_value(display_field, r.message)
}
frappe.call({
method: "erpnext.accounts.party.set_taxes",
args: {
"party": frm.doc.customer || frm.doc.supplier,
"party_type": (frm.doc.customer ? "Customer" : "Supplier"),
"posting_date": frm.doc.posting_date || frm.doc.transaction_date,
"company": frm.doc.company,
"billing_address": ((frm.doc.customer) ? (frm.doc.customer_address) : (frm.doc.supplier_address)),
"shipping_address": frm.doc.shipping_address_name
},
callback: function(r) {
if(r.message){
frm.set_value("taxes_and_charges", r.message)
}
}
});
}
})
}

View File

@@ -1,4 +1,5 @@
@border-color: #d1d8dd;
@light-border-color: #EBEFF2;
.web-long-description {
font-size: 18px;
@@ -35,6 +36,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid @light-border-color;
}
.product-image.missing-image {

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,20 @@
frappe.listview_settings['Sales Order'] = {
add_fields: ["base_grand_total", "customer_name", "currency", "delivery_date", "per_delivered", "per_billed",
"status"],
"status", "order_type"],
get_indicator: function(doc) {
if(doc.status==="Stopped") {
return [__("Stopped"), "darkgrey", "status,=,Stopped"];
} else if(flt(doc.per_delivered) < 100 && frappe.datetime.get_diff(doc.delivery_date) < 0) {
} else if (doc.order_type !== "Maintenance"
&& flt(doc.per_delivered, 2) < 100 && frappe.datetime.get_diff(doc.delivery_date) < 0) {
// to bill & overdue
return [__("Overdue"), "red", "per_delivered,<,100|delivery_date,<,Today|status,!=,Stopped"];
} else if(flt(doc.per_delivered) < 100 && doc.status!=="Stopped") {
} else if (doc.order_type !== "Maintenance"
&& flt(doc.per_delivered, 2) < 100 && doc.status!=="Stopped") {
// not delivered
if(flt(doc.per_billed) < 100) {
if(flt(doc.per_billed, 2) < 100) {
// not delivered & not billed
return [__("To Deliver and Bill"), "orange",
@@ -23,11 +26,14 @@ frappe.listview_settings['Sales Order'] = {
"per_delivered,<,100|per_billed,=,100|status,!=,Stopped"];
}
} else if(flt(doc.per_delivered) == 100 && flt(doc.per_billed) < 100 && doc.status!=="Stopped") {
} else if ((doc.order_type === "Maintenance" || flt(doc.per_delivered, 2) == 100)
&& flt(doc.per_billed, 2) < 100 && doc.status!=="Stopped") {
// to bill
return [__("To Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Stopped"];
} else if(flt(doc.per_delivered) == 100 && flt(doc.per_billed) == 100 && doc.status!=="Stopped") {
} else if((doc.order_type === "Maintenance" || flt(doc.per_delivered, 2) == 100)
&& flt(doc.per_billed, 2) == 100 && doc.status!=="Stopped") {
return [__("Completed"), "green", "per_delivered,=,100|per_billed,=,100|status,!=,Stopped"];
}

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

@@ -8,7 +8,7 @@
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"fieldname": "sales_person",
@@ -34,7 +34,7 @@
"width": "200px"
},
{
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"fieldname": "contact_no",
@@ -79,7 +79,7 @@
"unique": 0
},
{
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"fieldname": "allocated_percentage",
@@ -104,7 +104,7 @@
"width": "100px"
},
{
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"fieldname": "allocated_amount",
@@ -130,7 +130,7 @@
"width": "120px"
},
{
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"fieldname": "incentives",
@@ -185,7 +185,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-13 16:30:24.146848",
"modified": "2015-09-23 09:50:26.923187",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Team",

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

@@ -4,5 +4,11 @@
"doctype": "Customer Group",
"is_group": "No",
"parent_customer_group": "All Customer Groups"
},
{
"customer_group_name": "_Test Customer Group 1",
"doctype": "Customer Group",
"is_group": "No",
"parent_customer_group": "All Customer Groups"
}
]

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