Merge branch 'develop' into fixed_purchase_receipt_time_out_error_develop

This commit is contained in:
rohitwaghchaure 2020-02-11 19:56:11 +05:30 committed by GitHub
commit 490b843ecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
208 changed files with 6118 additions and 5493 deletions

8
.snyk Normal file
View File

@ -0,0 +1,8 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.0
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
SNYK-JS-LODASH-450202:
- cypress > getos > async > lodash:
patched: '2020-01-31T01:35:12.802Z'

View File

@ -1,3 +0,0 @@
{
"baseUrl": "http://test_site_ui:8000"
}

View File

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -1,31 +0,0 @@
context('Form', () => {
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
it('create a new opportunity', () => {
cy.visit('/desk#Form/Opportunity/New Opportunity 1');
cy.get('.page-title').should('contain', 'Not Saved');
cy.fill_field('opportunity_from', 'Customer', 'Select');
cy.fill_field('party_name', 'Test Customer', 'Link').blur();
cy.get('.primary-action').click();
cy.get('.page-title').should('contain', 'Open');
cy.get('.form-inner-toolbar button:contains("Lost")').click({ force: true });
cy.get('.modal input[data-fieldname="lost_reason"]').as('input');
cy.get('@input').focus().type('Higher', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("Higher Price")')
.click({ force: true });
cy.get('@input').focus().type('No Followup', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("No Followup")')
.click();
cy.fill_field('detailed_reason', 'Test Detailed Reason', 'Text');
cy.get('.modal button:contains("Declare Lost")').click({ force: true });
cy.get('.page-title').should('contain', 'Lost');
});
});

View File

@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// }

View File

@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -1,22 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// import frappe commands
import '../../../frappe/cypress/support/index';
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -30,7 +30,7 @@ def validate_service_stop_date(doc):
frappe.throw(_("Service Stop Date cannot be after Service End Date")) frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx))) frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def convert_deferred_expense_to_expense(start_date=None, end_date=None): def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2017-05-29 21:35:13.136357", "creation": "2017-05-29 21:35:13.136357",
@ -82,7 +83,7 @@
"default": "0", "default": "0",
"fieldname": "is_default", "fieldname": "is_default",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is the Default Account" "label": "Is Default Account"
}, },
{ {
"default": "0", "default": "0",
@ -211,7 +212,8 @@
"read_only": 1 "read_only": 1
} }
], ],
"modified": "2019-10-02 01:34:12.417601", "links": [],
"modified": "2020-01-29 20:42:26.458316",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@ -31,7 +31,7 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except AttributeError: except AttributeError:
msg = _('BankAccount.validate_iban() failed for empty IBAN') msg = 'BankAccount.validate_iban() failed for empty IBAN'
self.fail(msg=msg) self.fail(msg=msg)
for iban in valid_ibans: for iban in valid_ibans:
@ -39,11 +39,11 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except ValidationError: except ValidationError:
msg = _('BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)) msg = 'BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)
self.fail(msg=msg) self.fail(msg=msg)
for not_iban in invalid_ibans: for not_iban in invalid_ibans:
bank_account.iban = not_iban bank_account.iban = not_iban
msg = _('BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)) msg = 'BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)
with self.assertRaises(ValidationError, msg=msg): with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban() bank_account.validate_iban()

View File

@ -314,7 +314,7 @@ class BankStatementTransactionEntry(Document):
try: try:
reconcile_against_document(lst) reconcile_against_document(lst)
except: except:
frappe.throw(_("Exception occurred while reconciling {0}".format(payment.reference_name))) frappe.throw(_("Exception occurred while reconciling {0}").format(payment.reference_name))
def submit_payment_entries(self): def submit_payment_entries(self):
for payment in self.new_transaction_items: for payment in self.new_transaction_items:

View File

@ -49,7 +49,7 @@ class BankTransaction(StatusUpdater):
if paid_amount and allocated_amount: if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount): if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))) frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).").format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))
else: else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry) self.clear_simple_entry(payment_entry)

View File

@ -18,7 +18,7 @@ class CForm(Document):
`tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no) `tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no)
if inv and inv[0][0] != 'Yes': if inv and inv[0][0] != 'Yes':
frappe.throw(_("C-form is not applicable for Invoice: {0}".format(d.invoice_no))) frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
elif inv and inv[0][1] and inv[0][1] != self.name: elif inv and inv[0][1] and inv[0][1] != self.name:
frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}. frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}.

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
@ -123,7 +124,8 @@
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"modified": "2019-09-16 14:44:17.103548", "links": [],
"modified": "2020-01-28 13:50:23.430434",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
@ -162,7 +164,6 @@
"role": "Purchase User" "role": "Purchase User"
} }
], ],
"quick_entry": 1,
"search_fields": "parent_cost_center, is_group", "search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",

File diff suppressed because it is too large Load Diff

View File

@ -616,7 +616,7 @@ class JournalEntry(AccountsController):
d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")) d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed"))
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount) pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount: if d.debit > pending_amount:
frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.reference_name, pending_amount))) frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}").format(d.idx, d.reference_name, pending_amount))
def validate_credit_debit_note(self): def validate_credit_debit_note(self):
if self.stock_entry: if self.stock_entry:
@ -624,7 +624,7 @@ class JournalEntry(AccountsController):
frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry)) frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry))
if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}): if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}):
frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}".format(self.voucher_type, self.name, self.stock_entry))) frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}").format(self.voucher_type, self.name, self.stock_entry))
def validate_empty_accounts_table(self): def validate_empty_accounts_table(self):
if not self.get('accounts'): if not self.get('accounts'):

View File

@ -102,7 +102,9 @@ class PaymentEntry(AccountsController):
self.bank = bank_data.bank self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no self.bank_account_no = bank_data.bank_account_no
self.set(field, bank_data.account)
if not self.get(field):
self.set(field, bank_data.account)
def validate_allocated_amount(self): def validate_allocated_amount(self):
for d in self.get("references"): for d in self.get("references"):
@ -1003,7 +1005,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
# only Purchase Invoice can be blocked individually # only Purchase Invoice can be blocked individually
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date))) frappe.msgprint(_('{0} is on hold till {1}').format(doc.name, doc.release_date))
else: else:
pe.append("references", { pe.append("references", {
'reference_doctype': dt, 'reference_doctype': dt,

View File

@ -39,8 +39,8 @@ class PaymentRequest(Document):
ref_amount = get_amount(ref_doc) ref_amount = get_amount(ref_doc)
if existing_payment_request_amount + flt(self.grand_total)> ref_amount: if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount" frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
.format(self.reference_doctype))) .format(self.reference_doctype))
def validate_currency(self): def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
@ -53,14 +53,14 @@ class PaymentRequest(Document):
for subscription_plan in self.subscription_plans: for subscription_plan in self.subscription_plans:
payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway") payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway")
if payment_gateway != self.payment_gateway_account: if payment_gateway != self.payment_gateway_account:
frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(subscription_plan.name))) frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request').format(subscription_plan.name))
rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty) rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty)
amount += rate amount += rate
if amount != self.grand_total: if amount != self.grand_total:
frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.".format(self.grand_total, amount))) frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.").format(self.grand_total, amount))
def on_submit(self): def on_submit(self):
if self.payment_request_type == 'Outward': if self.payment_request_type == 'Outward':

View File

@ -3,6 +3,7 @@
"autoname": "Prompt", "autoname": "Prompt",
"creation": "2013-05-24 12:15:51", "creation": "2013-05-24 12:15:51",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB",
"field_order": [ "field_order": [
"disabled", "disabled",
"section_break_2", "section_break_2",
@ -50,6 +51,7 @@
"income_account", "income_account",
"expense_account", "expense_account",
"taxes_and_charges", "taxes_and_charges",
"tax_category",
"apply_discount_on", "apply_discount_on",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
@ -381,11 +383,17 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"modified": "2019-05-25 22:56:30.352693", "modified": "2020-01-24 15:52:03.797701",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Profile", "name": "POS Profile",

View File

@ -56,12 +56,12 @@ class PricingRule(Document):
if not self.selling and self.applicable_for in ["Customer", "Customer Group", if not self.selling and self.applicable_for in ["Customer", "Customer Group",
"Territory", "Sales Partner", "Campaign"]: "Territory", "Sales Partner", "Campaign"]:
throw(_("Selling must be checked, if Applicable For is selected as {0}" throw(_("Selling must be checked, if Applicable For is selected as {0}")
.format(self.applicable_for))) .format(self.applicable_for))
if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]: if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]:
throw(_("Buying must be checked, if Applicable For is selected as {0}" throw(_("Buying must be checked, if Applicable For is selected as {0}")
.format(self.applicable_for))) .format(self.applicable_for))
def validate_min_max_qty(self): def validate_min_max_qty(self):
if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty): if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty):
@ -243,7 +243,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if pricing_rule.coupon_code_based==1 and args.coupon_code==None: if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details return item_details
if not pricing_rule.validate_applied_rule: if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price": if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args) apply_price_discount_rule(pricing_rule, item_details, args)

View File

@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from frappe import MandatoryError from frappe import MandatoryError
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_item_price
class TestPricingRule(unittest.TestCase): class TestPricingRule(unittest.TestCase):
def setUp(self): def setUp(self):
@ -145,6 +147,52 @@ class TestPricingRule(unittest.TestCase):
self.assertEquals(details.get("margin_type"), "Percentage") self.assertEquals(details.get("margin_type"), "Percentage")
self.assertEquals(details.get("margin_rate_or_amount"), 10) self.assertEquals(details.get("margin_rate_or_amount"), 10)
def test_mixed_conditions_for_item_group(self):
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
make_item(item, {"item_group": "Products"})
make_item_price(item, "_Test Price List", 100)
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule for Item Group",
"apply_on": "Item Group",
"item_groups": [
{
"item_group": "Products",
},
{
"item_group": "Seed",
},
],
"selling": 1,
"mixed_conditions": 1,
"currency": "USD",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"applicable_for": "Customer Group",
"customer_group": "All Customer Groups",
"company": "_Test Company"
}
frappe.get_doc(test_record.copy()).insert()
args = frappe._dict({
"item_code": "Mixed Cond Item 1",
"item_group": "Products",
"company": "_Test Company",
"price_list": "_Test Price List",
"currency": "_Test Currency",
"doctype": "Sales Order",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
"customer_group": "_Test Customer Group",
"name": None
})
details = get_item_details(args)
self.assertEquals(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self): def test_pricing_rule_for_variants(self):
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from frappe import MandatoryError from frappe import MandatoryError

View File

@ -489,7 +489,7 @@ def get_pricing_rule_items(pr_doc):
for d in pr_doc.get(pricing_rule_apply_on): for d in pr_doc.get(pricing_rule_apply_on):
if apply_on == 'item_group': if apply_on == 'item_group':
get_child_item_groups(d.get(apply_on)) apply_on_data.extend(get_child_item_groups(d.get(apply_on)))
else: else:
apply_on_data.append(d.get(apply_on)) apply_on_data.append(d.get(apply_on))

View File

@ -908,7 +908,7 @@ class PurchaseInvoice(BuyingController):
if pi: if pi:
pi = pi[0][0] pi = pi[0][0]
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}".format(pi))) frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
def update_billing_status_in_pr(self, update_modified=True): def update_billing_status_in_pr(self, update_modified=True):
updated_pr = [] updated_pr = []

View File

@ -25,7 +25,7 @@ frappe.ui.form.on("Sales Invoice", {
if(frm.doc.docstatus == 1 && !frm.is_dirty() if(frm.doc.docstatus == 1 && !frm.is_dirty()
&& !frm.doc.is_return && !frm.doc.ewaybill) { && !frm.doc.is_return && !frm.doc.ewaybill) {
frm.add_custom_button('e-Way Bill JSON', () => { frm.add_custom_button('E-Way Bill JSON', () => {
var w = window.open( var w = window.open(
frappe.urllib.get_full_url( frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" "/api/method/erpnext.regional.india.utils.generate_ewb_json?"
@ -36,7 +36,7 @@ frappe.ui.form.on("Sales Invoice", {
if (!w) { if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return; frappe.msgprint(__("Please enable pop-ups")); return;
} }
}, __("Make")); }, __("Create"));
} }
} }

View File

@ -12,7 +12,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
for (let doc of selected_docs) { for (let doc of selected_docs) {
if (doc.docstatus !== 1) { if (doc.docstatus !== 1) {
frappe.throw(__("e-Way Bill JSON can only be generated from a submitted document")); frappe.throw(__("E-Way Bill JSON can only be generated from a submitted document"));
} }
} }
@ -29,5 +29,5 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
}; };
doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false); doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
}; };

View File

@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@ -373,7 +372,8 @@
"no_copy": 1, "no_copy": 1,
"options": "Sales Invoice", "options": "Sales Invoice",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "column_break_21", "fieldname": "column_break_21",
@ -1568,8 +1568,7 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "modified": "2020-02-10 04:57:11.221180",
"modified": "2019-12-30 19:15:59.580414",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -225,7 +225,7 @@ class SalesInvoice(SellingController):
total_amount_in_payments += payment.amount total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total invoice_total = self.rounded_total or self.grand_total
if total_amount_in_payments < invoice_total: if total_amount_in_payments < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
@ -420,6 +420,9 @@ class SalesInvoice(SellingController):
if pos: if pos:
self.allow_print_before_pay = pos.allow_print_before_pay self.allow_print_before_pay = pos.allow_print_before_pay
if not for_validate:
self.tax_category = pos.get("tax_category")
if not for_validate and not self.customer: if not for_validate and not self.customer:
self.customer = pos.customer self.customer = pos.customer
@ -1041,11 +1044,11 @@ class SalesInvoice(SellingController):
si_serial_nos = set(get_serial_nos(serial_nos)) si_serial_nos = set(get_serial_nos(serial_nos))
if si_serial_nos - dn_serial_nos: if si_serial_nos - dn_serial_nos:
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx))) frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note").format(item.idx))
if item.serial_no and cint(item.qty) != len(si_serial_nos): if item.serial_no and cint(item.qty) != len(si_serial_nos):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format( frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
item.idx, item.qty, item.item_code, len(si_serial_nos)))) item.idx, item.qty, item.item_code, len(si_serial_nos)))
def validate_serial_against_sales_invoice(self): def validate_serial_against_sales_invoice(self):
""" check if serial number is already used in other sales invoice """ """ check if serial number is already used in other sales invoice """
@ -1064,8 +1067,8 @@ class SalesInvoice(SellingController):
and self.name != serial_no_details.sales_invoice: and self.name != serial_no_details.sales_invoice:
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
if sales_invoice_company == self.company: if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
.format(serial_no, serial_no_details.sales_invoice))) .format(serial_no, serial_no_details.sales_invoice))
def update_project(self): def update_project(self):
if self.project: if self.project:

View File

@ -82,7 +82,7 @@ class ShippingRule(Document):
if not shipping_country: if not shipping_country:
frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule')) frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule'))
if shipping_country not in [d.country for d in self.countries]: if shipping_country not in [d.country for d in self.countries]:
frappe.throw(_('Shipping rule not applicable for country {0}'.format(shipping_country))) frappe.throw(_('Shipping rule not applicable for country {0}').format(shipping_country))
def add_shipping_rule_to_tax_table(self, doc, shipping_amount): def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
shipping_charge = { shipping_charge = {

View File

@ -195,7 +195,7 @@ class Subscription(Document):
doc = frappe.get_doc('Sales Invoice', current.invoice) doc = frappe.get_doc('Sales Invoice', current.invoice)
return doc return doc
else: else:
frappe.throw(_('Invoice {0} no longer exists'.format(current.invoice))) frappe.throw(_('Invoice {0} no longer exists').format(current.invoice))
def is_new_subscription(self): def is_new_subscription(self):
""" """
@ -338,7 +338,7 @@ class Subscription(Document):
# Check invoice dates and make sure it doesn't have outstanding invoices # Check invoice dates and make sure it doesn't have outstanding invoices
return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice()
def is_current_invoice_paid(self): def is_current_invoice_paid(self):
if self.is_new_subscription(): if self.is_new_subscription():
return False return False
@ -346,7 +346,7 @@ class Subscription(Document):
last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice) last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice)
if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid': if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid':
return True return True
return False return False
def process_for_active(self): def process_for_active(self):
@ -388,7 +388,7 @@ class Subscription(Document):
""" """
current_invoice = self.get_current_invoice() current_invoice = self.get_current_invoice()
if not current_invoice: if not current_invoice:
frappe.throw(_('Current invoice {0} is missing'.format(current_invoice.invoice))) frappe.throw(_('Current invoice {0} is missing').format(current_invoice.invoice))
else: else:
if self.is_not_outstanding(current_invoice): if self.is_not_outstanding(current_invoice):
self.status = 'Active' self.status = 'Active'

View File

@ -95,7 +95,7 @@ class TaxRule(Document):
if tax_rule: if tax_rule:
if tax_rule[0].priority == self.priority: if tax_rule[0].priority == self.priority:
frappe.throw(_("Tax Rule Conflicts with {0}".format(tax_rule[0].name)), ConflictingTaxRule) frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
def validate_use_for_shopping_cart(self): def validate_use_for_shopping_cart(self):
'''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one''' '''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint from frappe.utils import cint, cstr
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
@ -129,13 +129,13 @@ def get_account_type_based_gl_data(company, start_date, end_date, account_type,
cond = "" cond = ""
filters = frappe._dict(filters) filters = frappe._dict(filters)
if filters.finance_book: if filters.include_default_book_entries:
cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" %(frappe.db.escape(filters.finance_book)) company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
if filters.include_default_book_entries: cond = """ AND (finance_book in (%s, %s, '') OR finance_book IS NULL)
company_fb = frappe.db.get_value("Company", company, 'default_finance_book') """ %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
else:
cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" %(frappe.db.escape(cstr(filters.finance_book)))
cond = """ AND (finance_book in (%s, %s, '') OR finance_book IS NULL)
""" %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
gl_sum = frappe.db.sql_list(""" gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit) select sum(credit) - sum(debit)

View File

@ -387,11 +387,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
if from_date: if from_date:
additional_conditions.append("gl.posting_date >= %(from_date)s") additional_conditions.append("gl.posting_date >= %(from_date)s")
if filters.get("finance_book"): if filters.get("include_default_book_entries"):
if filters.get("include_default_book_entries"): additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)") else:
else: additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@ -13,7 +13,7 @@ import frappe, erpnext
from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe import _ from frappe import _
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate) from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr)
from six import itervalues from six import itervalues
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@ -175,7 +175,7 @@ def calculate_values(
d = accounts_by_name.get(entry.account) d = accounts_by_name.get(entry.account)
if not d: if not d:
frappe.msgprint( frappe.msgprint(
_("Could not retrieve information for {0}.".format(entry.account)), title="Error", _("Could not retrieve information for {0}.").format(entry.account), title="Error",
raise_exception=1 raise_exception=1
) )
for period in period_list: for period in period_list:
@ -348,40 +348,42 @@ def set_gl_entries_by_account(
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters) additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
accounts = frappe.db.sql_list("""select name from `tabAccount` accounts = frappe.db.sql_list("""select name from `tabAccount`
where lft >= %s and rgt <= %s""", (root_lft, root_rgt)) where lft >= %s and rgt <= %s and company = %s""", (root_lft, root_rgt, company))
additional_conditions += " and account in ({})"\
.format(", ".join([frappe.db.escape(d) for d in accounts]))
gl_filters = { if accounts:
"company": company, additional_conditions += " and account in ({})"\
"from_date": from_date, .format(", ".join([frappe.db.escape(d) for d in accounts]))
"to_date": to_date,
"finance_book": filters.get("finance_book")
}
if filters.get("include_default_book_entries"): gl_filters = {
gl_filters["company_fb"] = frappe.db.get_value("Company", "company": company,
company, 'default_finance_book') "from_date": from_date,
"to_date": to_date,
"finance_book": cstr(filters.get("finance_book"))
}
for key, value in filters.items(): if filters.get("include_default_book_entries"):
if value: gl_filters["company_fb"] = frappe.db.get_value("Company",
gl_filters.update({ company, 'default_finance_book')
key: value
})
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` for key, value in filters.items():
where company=%(company)s if value:
{additional_conditions} gl_filters.update({
and posting_date <= %(to_date)s key: value
order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec })
if filters and filters.get('presentation_currency'): gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
convert_to_presentation_currency(gl_entries, get_currency(filters)) where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec
for entry in gl_entries: if filters and filters.get('presentation_currency'):
gl_entries_by_account.setdefault(entry.account, []).append(entry) convert_to_presentation_currency(gl_entries, get_currency(filters))
return gl_entries_by_account for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
return gl_entries_by_account
def get_additional_conditions(from_date, ignore_closing_entries, filters): def get_additional_conditions(from_date, ignore_closing_entries, filters):
@ -406,12 +408,11 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
filters.cost_center = get_cost_centers_with_children(filters.cost_center) filters.cost_center = get_cost_centers_with_children(filters.cost_center)
additional_conditions.append("cost_center in %(cost_center)s") additional_conditions.append("cost_center in %(cost_center)s")
if filters.get("finance_book"): if filters.get("include_default_book_entries"):
if filters.get("include_default_book_entries"): additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)") else:
else: additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
if accounting_dimensions: if accounting_dimensions:
for dimension in accounting_dimensions: for dimension in accounting_dimensions:
if filters.get(dimension): if filters.get(dimension):
@ -430,7 +431,7 @@ def get_cost_centers_with_children(cost_centers):
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_cost_centers += [c.name for c in children] all_cost_centers += [c.name for c in children]
else: else:
frappe.throw(_("Cost Center: {0} does not exist".format(d))) frappe.throw(_("Cost Center: {0} does not exist").format(d))
return list(set(all_cost_centers)) return list(set(all_cost_centers))

View File

@ -373,19 +373,19 @@ def get_columns(filters):
"width": 180 "width": 180
}, },
{ {
"label": _("Debit ({0})".format(currency)), "label": _("Debit ({0})").format(currency),
"fieldname": "debit", "fieldname": "debit",
"fieldtype": "Float", "fieldtype": "Float",
"width": 100 "width": 100
}, },
{ {
"label": _("Credit ({0})".format(currency)), "label": _("Credit ({0})").format(currency),
"fieldname": "credit", "fieldname": "credit",
"fieldtype": "Float", "fieldtype": "Float",
"width": 100 "width": 100
}, },
{ {
"label": _("Balance ({0})".format(currency)), "label": _("Balance ({0})").format(currency),
"fieldname": "balance", "fieldname": "balance",
"fieldtype": "Float", "fieldtype": "Float",
"width": 130 "width": 130

View File

@ -34,6 +34,20 @@ frappe.query_reports["Item-wise Purchase Register"] = {
"label": __("Mode of Payment"), "label": __("Mode of Payment"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Mode of Payment" "options": "Mode of Payment"
},
{
"label": __("Group By"),
"fieldname": "group_by",
"fieldtype": "Select",
"options": ["Supplier", "Item Group", "Item", "Invoice"]
} }
] ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
} }

View File

@ -5,7 +5,9 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import get_tax_accounts from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions)
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -13,7 +15,7 @@ def execute(filters=None):
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {} if not filters: filters = {}
filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]}) filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]})
columns = get_columns(additional_table_columns) columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company) company_currency = erpnext.get_company_currency(filters.company)
@ -23,16 +25,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Data",
"width": 80
})
po_pr_map = get_purchase_receipts_against_purchase_order(item_list) po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = [] data = []
total_row_map = {}
skip_total_row = 0
prev_group_by_value = ''
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice')
for d in item_list: for d in item_list:
if not d.stock_qty: if not d.stock_qty:
continue continue
@ -44,51 +46,243 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, [])) purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
expense_account = d.expense_account or aii_account_map.get(d.company) expense_account = d.expense_account or aii_account_map.get(d.company)
row = [d.item_code, d.item_name, d.item_group, d.description, d.parent, d.posting_date, d.supplier,
d.supplier_name] row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.supplier,
'customer_name': d.supplier_name
}
if additional_query_columns: if additional_query_columns:
for col in additional_query_columns: for col in additional_query_columns:
row.append(d.get(col)) row.update({
col: d.get(col)
})
row += [ row.update({
d.credit_to, d.mode_of_payment, d.project, d.company, d.purchase_order, 'credit_to': d.credit_to,
purchase_receipt, expense_account, d.stock_qty, d.stock_uom, d.base_net_amount / d.stock_qty, d.base_net_amount 'mode_of_payment': d.mode_of_payment,
] 'project': d.project,
'company': d.company,
'purchase_order': d.purchase_order,
'purchase_receipt': d.purchase_receipt,
'expense_account': expense_account,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom,
'rate': d.base_net_amount / d.stock_qty,
'amount': d.base_net_amount
})
total_tax = 0 total_tax = 0
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
})
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get("tax_amount"))
row += [total_tax, d.base_net_amount + total_tax, company_currency] row.update({
'total_tax': total_tax,
'total': d.base_net_amount + total_tax,
'currency': company_currency
})
if filters.get('group_by'):
row.update({'percent_gt': flt(row['total']/grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns)
add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
data.append(row) data.append(row)
return columns, data if filters.get('group_by'):
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
data.append(total_row_map.get('total_row'))
skip_total_row = 1
return columns, data, None, None, None, skip_total_row
def get_columns(additional_table_columns): def get_columns(additional_table_columns, filters):
columns = [
_("Item Code") + ":Link/Item:120", _("Item Name") + "::120", columns = []
_("Item Group") + ":Link/Item Group:100", "Description::150", _("Invoice") + ":Link/Purchase Invoice:120",
_("Posting Date") + ":Date:80", _("Supplier") + ":Link/Supplier:120", if filters.get('group_by') != ('Item'):
"Supplier Name::120" columns.extend(
] [
{
'label': _('Item Code'),
'fieldname': 'item_code',
'fieldtype': 'Link',
'options': 'Item',
'width': 120
},
{
'label': _('Item Name'),
'fieldname': 'item_name',
'fieldtype': 'Data',
'width': 120
}
]
)
if filters.get('group_by') not in ('Item', 'Item Group'):
columns.extend([
{
'label': _('Item Group'),
'fieldname': 'item_group',
'fieldtype': 'Link',
'options': 'Item Group',
'width': 120
}
])
columns.extend([
{
'label': _('Description'),
'fieldname': 'description',
'fieldtype': 'Data',
'width': 150
},
{
'label': _('Invoice'),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Purchase Invoice',
'width': 120
},
{
'label': _('Posting Date'),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 120
}
])
if filters.get('group_by') != 'Supplier':
columns.extend([
{
'label': _('Supplier'),
'fieldname': 'supplier',
'fieldtype': 'Link',
'options': 'Supplier',
'width': 120
},
{
'label': _('Supplier Name'),
'fieldname': 'supplier_name',
'fieldtype': 'Data',
'width': 120
}
])
if additional_table_columns: if additional_table_columns:
columns += additional_table_columns columns += additional_table_columns
columns += [ columns += [
"Payable Account:Link/Account:120", {
_("Mode of Payment") + ":Link/Mode of Payment:80", _("Project") + ":Link/Project:80", 'label': _('Payable Account'),
_("Company") + ":Link/Company:100", _("Purchase Order") + ":Link/Purchase Order:100", 'fieldname': 'credit_to',
_("Purchase Receipt") + ":Link/Purchase Receipt:100", _("Expense Account") + ":Link/Account:140", 'fieldtype': 'Link',
_("Stock Qty") + ":Float:120", _("Stock UOM") + "::100", 'options': 'Account',
_("Rate") + ":Currency/currency:120", _("Amount") + ":Currency/currency:120" 'width': 80
},
{
'label': _('Mode Of Payment'),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
},
{
'label': _('Project'),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'Project',
'width': 80
},
{
'label': _('Company'),
'fieldname': 'company',
'fieldtype': 'Link',
'options': 'Company',
'width': 80
},
{
'label': _('Purchase Order'),
'fieldname': 'purchase_order',
'fieldtype': 'Link',
'options': 'Purchase Order',
'width': 100
},
{
'label': _("Purchase Receipt"),
'fieldname': 'Purchase Receipt',
'fieldtype': 'Link',
'options': 'Purchase Receipt',
'width': 100
},
{
'label': _('Expense Account'),
'fieldname': 'expense_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 100
},
{
'label': _('Stock Qty'),
'fieldname': 'stock_qty',
'fieldtype': 'Float',
'width': 100
},
{
'label': _('Stock UOM'),
'fieldname': 'stock_uom',
'fieldtype': 'Link',
'options': 'UOM',
'width': 100
},
{
'label': _('Rate'),
'fieldname': 'rate',
'fieldtype': 'Float',
'options': 'currency',
'width': 100
},
{
'label': _('Amount'),
'fieldname': 'amount',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'fieldname': 'currency',
'label': _('Currency'),
'fieldtype': 'Currency',
'width': 80,
'hidden': 1
}
] ]
if filters.get('group_by'):
columns.append({
'label': _('% Of Grand Total'),
'fieldname': 'percent_gt',
'fieldtype': 'Float',
'width': 80
})
return columns return columns
def get_conditions(filters): def get_conditions(filters):
@ -103,6 +297,11 @@ def get_conditions(filters):
if filters.get(opts[0]): if filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]
if not filters.get("group_by"):
conditions += "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
else:
conditions += get_group_by_conditions(filters, 'Purchase Invoice')
return conditions return conditions
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
@ -129,7 +328,6 @@ def get_items(filters, additional_query_columns):
from `tabPurchase Invoice`, `tabPurchase Invoice Item` from `tabPurchase Invoice`, `tabPurchase Invoice Item`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s %s `tabPurchase Invoice`.docstatus = 1 %s %s
order by `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc
""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) """.format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1)
def get_aii_accounts(): def get_aii_accounts():

View File

@ -4,48 +4,62 @@
frappe.query_reports["Item-wise Sales Register"] = { frappe.query_reports["Item-wise Sales Register"] = {
"filters": [ "filters": [
{ {
"fieldname":"date_range", "fieldname": "date_range",
"label": __("Date Range"), "label": __("Date Range"),
"fieldtype": "DateRange", "fieldtype": "DateRange",
"default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()], "default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()],
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname":"customer", "fieldname": "customer",
"label": __("Customer"), "label": __("Customer"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Customer" "options": "Customer"
}, },
{ {
"fieldname":"company", "fieldname": "company",
"label": __("Company"), "label": __("Company"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Company", "options": "Company",
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{ {
"fieldname":"mode_of_payment", "fieldname": "mode_of_payment",
"label": __("Mode of Payment"), "label": __("Mode of Payment"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Mode of Payment" "options": "Mode of Payment"
}, },
{ {
"fieldname":"warehouse", "fieldname": "warehouse",
"label": __("Warehouse"), "label": __("Warehouse"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Warehouse" "options": "Warehouse"
}, },
{ {
"fieldname":"brand", "fieldname": "brand",
"label": __("Brand"), "label": __("Brand"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Brand" "options": "Brand"
}, },
{ {
"fieldname":"item_group", "fieldname": "item_group",
"label": __("Item Group"), "label": __("Item Group"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Item Group" "options": "Item Group"
},
{
"label": __("Group By"),
"fieldname": "group_by",
"fieldtype": "Select",
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"]
} }
] ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
} }

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
@ -15,23 +15,25 @@ def execute(filters=None):
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {} if not filters: filters = {}
filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]}) filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns) item_list = get_items(filters, additional_query_columns)
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Data",
"width": 80
})
mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list])) mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list]))
so_dn_map = get_delivery_notes_against_sales_order(item_list) so_dn_map = get_delivery_notes_against_sales_order(item_list)
data = [] data = []
total_row_map = {}
skip_total_row = 0
prev_group_by_value = ''
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice')
for d in item_list: for d in item_list:
delivery_note = None delivery_note = None
if d.delivery_note: if d.delivery_note:
@ -42,57 +44,285 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not delivery_note and d.update_stock: if not delivery_note and d.update_stock:
delivery_note = d.parent delivery_note = d.parent
row = [d.item_code, d.item_name, d.item_group, d.description, d.parent, d.posting_date, d.customer, d.customer_name] row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.customer,
'customer_name': d.customer_name,
'customer_group': d.customer_group,
}
if additional_query_columns: if additional_query_columns:
for col in additional_query_columns: for col in additional_query_columns:
row.append(d.get(col)) row.update({
col: d.get(col)
})
row += [ row.update({
d.customer_group, d.debit_to, ", ".join(mode_of_payments.get(d.parent, [])), 'debit_to': d.debit_to,
d.territory, d.project, d.company, d.sales_order, 'mode_of_payment': ", ".join(mode_of_payments.get(d.parent, [])),
delivery_note, d.income_account, d.cost_center, d.stock_qty, d.stock_uom 'territory': d.territory,
] 'project': d.project,
'company': d.company,
'sales_order': d.sales_order,
'delivery_note': d.delivery_note,
'income_account': d.income_account,
'cost_center': d.cost_center,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom
})
if d.stock_uom != d.uom and d.stock_qty: if d.stock_uom != d.uom and d.stock_qty:
row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount] row.update({
'rate': (d.base_net_rate * d.qty)/d.stock_qty,
'amount': d.base_net_amount
})
else: else:
row += [d.base_net_rate, d.base_net_amount] row.update({
'rate': d.base_net_rate,
'amount': d.base_net_amount
})
total_tax = 0 total_tax = 0
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
})
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get("tax_amount"))
row += [total_tax, d.base_net_amount + total_tax, company_currency] row.update({
'total_tax': total_tax,
'total': d.base_net_amount + total_tax,
'currency': company_currency
})
if filters.get('group_by'):
row.update({'percent_gt': flt(row['total']/grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns)
add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
data.append(row) data.append(row)
return columns, data if filters.get('group_by'):
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
data.append(total_row_map.get('total_row'))
skip_total_row = 1
return columns, data, None, None, None, skip_total_row
def get_columns(additional_table_columns): def get_columns(additional_table_columns, filters):
columns = [ columns = []
_("Item Code") + ":Link/Item:120", _("Item Name") + "::120",
_("Item Group") + ":Link/Item Group:100", "Description::150", _("Invoice") + ":Link/Sales Invoice:120", if filters.get('group_by') != ('Item'):
_("Posting Date") + ":Date:80", _("Customer") + ":Link/Customer:120", columns.extend(
_("Customer Name") + "::120"] [
{
'label': _('Item Code'),
'fieldname': 'item_code',
'fieldtype': 'Link',
'options': 'Item',
'width': 120
},
{
'label': _('Item Name'),
'fieldname': 'item_name',
'fieldtype': 'Data',
'width': 120
}
]
)
if filters.get('group_by') not in ('Item', 'Item Group'):
columns.extend([
{
'label': _('Item Group'),
'fieldname': 'item_group',
'fieldtype': 'Link',
'options': 'Item Group',
'width': 120
}
])
columns.extend([
{
'label': _('Description'),
'fieldname': 'description',
'fieldtype': 'Data',
'width': 150
},
{
'label': _('Invoice'),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Sales Invoice',
'width': 120
},
{
'label': _('Posting Date'),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 120
}
])
if filters.get('group_by') != 'Customer':
columns.extend([
{
'label': _('Customer Group'),
'fieldname': 'customer_group',
'fieldtype': 'Link',
'options': 'Customer Group',
'width': 120
}
])
if filters.get('group_by') not in ('Customer', 'Customer Group'):
columns.extend([
{
'label': _('Customer'),
'fieldname': 'customer',
'fieldtype': 'Link',
'options': 'Customer',
'width': 120
},
{
'label': _('Customer Name'),
'fieldname': 'customer_name',
'fieldtype': 'Data',
'width': 120
}
])
if additional_table_columns: if additional_table_columns:
columns += additional_table_columns columns += additional_table_columns
columns += [ columns += [
_("Customer Group") + ":Link/Customer Group:120", {
_("Receivable Account") + ":Link/Account:120", 'label': _('Receivable Account'),
_("Mode of Payment") + "::120", _("Territory") + ":Link/Territory:80", 'fieldname': 'debit_to',
_("Project") + ":Link/Project:80", _("Company") + ":Link/Company:100", 'fieldtype': 'Link',
_("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100", 'options': 'Account',
_("Income Account") + ":Link/Account:140", _("Cost Center") + ":Link/Cost Center:140", 'width': 80
_("Stock Qty") + ":Float:120", _("Stock UOM") + "::100", },
_("Rate") + ":Currency/currency:120", {
_("Amount") + ":Currency/currency:120" 'label': _('Mode Of Payment'),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
}
] ]
if filters.get('group_by') != 'Terriotory':
columns.extend([
{
'label': _("Territory"),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 80
}
])
columns += [
{
'label': _('Project'),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'Project',
'width': 80
},
{
'label': _('Company'),
'fieldname': 'company',
'fieldtype': 'Link',
'options': 'Company',
'width': 80
},
{
'label': _('Sales Order'),
'fieldname': 'sales_order',
'fieldtype': 'Link',
'options': 'Sales Order',
'width': 100
},
{
'label': _("Delivery Note"),
'fieldname': 'delivery_note',
'fieldtype': 'Link',
'options': 'Delivery Note',
'width': 100
},
{
'label': _('Income Account'),
'fieldname': 'income_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 100
},
{
'label': _("Cost Center"),
'fieldname': 'cost_center',
'fieldtype': 'Link',
'options': 'Cost Center',
'width': 100
},
{
'label': _('Stock Qty'),
'fieldname': 'stock_qty',
'fieldtype': 'Float',
'width': 100
},
{
'label': _('Stock UOM'),
'fieldname': 'stock_uom',
'fieldtype': 'Link',
'options': 'UOM',
'width': 100
},
{
'label': _('Rate'),
'fieldname': 'rate',
'fieldtype': 'Float',
'options': 'currency',
'width': 100
},
{
'label': _('Amount'),
'fieldname': 'amount',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'fieldname': 'currency',
'label': _('Currency'),
'fieldtype': 'Currency',
'width': 80,
'hidden': 1
}
]
if filters.get('group_by'):
columns.append({
'label': _('% Of Grand Total'),
'fieldname': 'percent_gt',
'fieldtype': 'Float',
'width': 80
})
return columns return columns
def get_conditions(filters): def get_conditions(filters):
@ -112,24 +342,32 @@ def get_conditions(filters):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"): if filters.get("warehouse"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"): if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"): if filters.get("item_group"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
if not filters.get("group_by"):
conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
else:
conditions += get_group_by_conditions(filters, 'Sales Invoice')
return conditions return conditions
def get_group_by_conditions(filters, doctype):
if filters.get("group_by") == 'Invoice':
return "ORDER BY `tab{0} Item`.parent desc".format(doctype)
elif filters.get("group_by") == 'Item':
return "ORDER BY `tab{0} Item`.`item_code`".format(doctype)
elif filters.get("group_by") == 'Item Group':
return "ORDER BY `tab{0} Item`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
elif filters.get("group_by") in ('Customer', 'Customer Group', 'Territory', 'Supplier'):
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice") match_conditions = frappe.build_match_conditions("Sales Invoice")
@ -156,9 +394,8 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 %s %s and `tabSales Invoice`.docstatus = 1 {1} {2}
order by `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_code desc """.format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec
""".format(additional_query_columns or '') % (conditions, match_conditions), filters, as_dict=1)
def get_delivery_notes_against_sales_order(item_list): def get_delivery_notes_against_sales_order(item_list):
so_dn_map = frappe._dict() so_dn_map = frappe._dict()
@ -177,6 +414,15 @@ def get_delivery_notes_against_sales_order(item_list):
return so_dn_map return so_dn_map
def get_grand_total(filters, doctype):
return frappe.db.sql(""" SELECT
SUM(`tab{0}`.base_grand_total)
FROM `tab{0}`
WHERE `tab{0}`.docstatus = 1
and posting_date between %s and %s
""".format(doctype), (filters.get('from_date'), filters.get('to_date')))[0][0] #nosec
def get_deducted_taxes(): def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
@ -264,9 +510,117 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_columns.sort() tax_columns.sort()
for desc in tax_columns: for desc in tax_columns:
columns.append(desc + " Rate:Data:80") columns.append({
columns.append(desc + " Amount:Currency/currency:100") 'label': _(desc + ' Rate'),
'fieldname': frappe.scrub(desc + ' Rate'),
'fieldtype': 'Float',
'width': 100
})
columns += ["Total Tax:Currency/currency:80", "Total:Currency/currency:100"] columns.append({
'label': _(desc + ' Amount'),
'fieldname': frappe.scrub(desc + ' Amount'),
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
})
columns += [
{
'label': _('Total Tax'),
'fieldname': 'total_tax',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'label': _('Total'),
'fieldname': 'total',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
}
]
return itemised_tax, tax_columns return itemised_tax, tax_columns
def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns):
if prev_group_by_value != item.get(group_by_field, ''):
if prev_group_by_value:
total_row = total_row_map.get(prev_group_by_value)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
prev_group_by_value = item.get(group_by_field, '')
total_row_map.setdefault(item.get(group_by_field, ''), {
subtotal_display_field: get_display_value(filters, group_by_field, item),
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,
'total_tax': 0.0,
'total': 0.0,
'percent_gt': 0.0
})
total_row_map.setdefault('total_row', {
subtotal_display_field: "Total",
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,
'total_tax': 0.0,
'total': 0.0,
'percent_gt': 0.0
})
return data, prev_group_by_value
def get_display_value(filters, group_by_field, item):
if filters.get('group_by') == 'Item':
if item.get('item_code') != item.get('item_name'):
value = cstr(item.get('item_code')) + "<br><br>" + \
"<span style='font-weight: normal'>" + cstr(item.get('item_name')) + "</span>"
else:
value = item.get('item_code', '')
elif filters.get('group_by') in ('Customer', 'Supplier'):
party = frappe.scrub(filters.get('group_by'))
if item.get(party) != item.get(party+'_name'):
value = item.get(party) + "<br><br>" + \
"<span style='font-weight: normal'>" + item.get(party+'_name') + "</span>"
else:
value = item.get(party)
else:
value = item.get(group_by_field)
return value
def get_group_by_and_display_fields(filters):
if filters.get('group_by') == 'Item':
group_by_field = 'item_code'
subtotal_display_field = 'invoice'
elif filters.get('group_by') == 'Invoice':
group_by_field = 'parent'
subtotal_display_field = 'item_code'
else:
group_by_field = frappe.scrub(filters.get('group_by'))
subtotal_display_field = 'item_code'
return group_by_field, subtotal_display_field
def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
total_row = total_row_map.get(group_by_value)
total_row['stock_qty'] += item['stock_qty']
total_row['amount'] += item['amount']
total_row['total_tax'] += item['total_tax']
total_row['total'] += item['total']
total_row['percent_gt'] += item['percent_gt']
for tax in tax_columns:
total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])

View File

@ -139,7 +139,7 @@ def get_columns(invoice_list, additional_table_columns):
columns +=[ columns +=[
{ {
'label': _("Custmer Group"), 'label': _("Customer Group"),
'fieldname': 'customer_group', 'fieldname': 'customer_group',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'Customer Group', 'options': 'Customer Group',
@ -175,7 +175,7 @@ def get_columns(invoice_list, additional_table_columns):
'label': _("Project"), 'label': _("Project"),
'fieldname': 'project', 'fieldname': 'project',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'project', 'options': 'Project',
'width': 80 'width': 80
}, },
{ {

View File

@ -513,7 +513,7 @@ def remove_ref_doc_link_from_jv(ref_type, ref_no):
where reference_type=%s and reference_name=%s where reference_type=%s and reference_name=%s
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no)) and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
frappe.msgprint(_("Journal Entries {0} are un-linked".format("\n".join(linked_jv)))) frappe.msgprint(_("Journal Entries {0} are un-linked").format("\n".join(linked_jv)))
def remove_ref_doc_link_from_pe(ref_type, ref_no): def remove_ref_doc_link_from_pe(ref_type, ref_no):
linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference` linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference`
@ -536,7 +536,7 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount, where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount,
pe_doc.unallocated_amount, now(), frappe.session.user, pe)) pe_doc.unallocated_amount, now(), frappe.session.user, pe))
frappe.msgprint(_("Payment Entries {0} are un-linked".format("\n".join(linked_pe)))) frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
@frappe.whitelist() @frappe.whitelist()
def get_company_default(company, fieldname): def get_company_default(company, fieldname):

View File

@ -44,7 +44,7 @@ class CropCycle(Document):
self.import_disease_tasks(disease.disease, disease.start_date) self.import_disease_tasks(disease.disease, disease.start_date)
disease.tasks_created = True disease.tasks_created = True
frappe.msgprint(_("Tasks have been created for managing the {0} disease (on row {1})".format(disease.disease, disease.idx))) frappe.msgprint(_("Tasks have been created for managing the {0} disease (on row {1})").format(disease.disease, disease.idx))
def import_disease_tasks(self, disease, start_date): def import_disease_tasks(self, disease, start_date):
disease_doc = frappe.get_doc('Disease', disease) disease_doc = frappe.get_doc('Disease', disease)

View File

@ -589,7 +589,7 @@ def transfer_asset(args):
frappe.db.commit() frappe.db.commit()
frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>".format(movement_entry.name))) frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
@frappe.whitelist() @frappe.whitelist()
def get_item_details(item_code, asset_category): def get_item_details(item_code, asset_category):
@ -611,7 +611,7 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
if asset: if asset:
account = get_asset_category_account(account_name, asset=asset, account = get_asset_category_account(account_name, asset=asset,
asset_category = asset_category, company = company) asset_category = asset_category, company = company)
if not asset and not account: if not asset and not account:
account = get_asset_category_account(account_name, asset_category = asset_category, company = company) account = get_asset_category_account(account_name, asset_category = asset_category, company = company)

View File

@ -10,7 +10,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
def post_depreciation_entries(date=None): def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")): if not cint(frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")):
return return
if not date: if not date:

View File

@ -1,580 +1,146 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:location_name", "autoname": "field:location_name",
"beta": 0,
"creation": "2018-05-07 12:49:22.595974", "creation": "2018-05-07 12:49:22.595974",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"location_name",
"parent_location",
"cb_details",
"is_container",
"is_group",
"sb_location_details",
"latitude",
"longitude",
"cb_latlong",
"area",
"area_uom",
"sb_geolocation",
"location",
"tree_details",
"lft",
"rgt",
"old_parent"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "location_name", "fieldname": "location_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Location Name", "label": "Location Name",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "parent_location", "fieldname": "parent_location",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Parent Location", "label": "Parent Location",
"length": 0,
"no_copy": 0,
"options": "Location", "options": "Location",
"permlevel": 0, "search_index": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_details", "fieldname": "cb_details",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Check if it is a hydroponic unit", "description": "Check if it is a hydroponic unit",
"fieldname": "is_container", "fieldname": "is_container",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Is Container"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Container",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 1, "bold": 1,
"collapsible": 0, "default": "0",
"columns": 0,
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Is Group"
"label": "Is Group",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_location_details", "fieldname": "sb_location_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Location Details"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Location Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "parent_location.latitude", "fetch_from": "parent_location.latitude",
"fieldname": "latitude", "fieldname": "latitude",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "label": "Latitude"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Latitude",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "parent_location.longitude", "fetch_from": "parent_location.longitude",
"fieldname": "longitude", "fieldname": "longitude",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "label": "Longitude"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Longitude",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_latlong", "fieldname": "cb_latlong",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "area", "fieldname": "area",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Area", "label": "Area",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.area", "depends_on": "eval:doc.area",
"fieldname": "area_uom", "fieldname": "area_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Area UOM", "label": "Area UOM",
"length": 0, "options": "UOM"
"no_copy": 0,
"options": "UOM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_geolocation", "fieldname": "sb_geolocation",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "location", "fieldname": "location",
"fieldtype": "Geolocation", "fieldtype": "Geolocation",
"hidden": 0, "label": "Location"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Location",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "tree_details", "fieldname": "tree_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0, "label": "Tree Details"
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tree Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lft", "fieldname": "lft",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "lft", "label": "lft",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rgt", "fieldname": "rgt",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "rgt", "label": "rgt",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "old_parent", "fieldname": "old_parent",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Old Parent", "label": "Old Parent",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-01-28 13:52:22.513425",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-11 13:36:30.999405",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Location", "name": "Location",
@ -582,127 +148,78 @@
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Agriculture Manager", "role": "Agriculture Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Agriculture User", "role": "Agriculture User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -43,7 +43,7 @@ class SupplierScorecardPeriod(Document):
try: try:
crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(self.get_eval_statement(crit.formula), None, {'max':max, 'min': min}))) crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(self.get_eval_statement(crit.formula), None, {'max':max, 'min': min})))
except Exception: except Exception:
frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.".format(crit.criteria_name)),frappe.ValidationError) frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.").format(crit.criteria_name),frappe.ValidationError)
crit.score = 0 crit.score = 0
def calculate_score(self): def calculate_score(self):

View File

@ -141,13 +141,13 @@ def get_conditions(filters):
conditions = "" conditions = ""
if filters.get("company"): if filters.get("company"):
conditions += " AND company='%s'"% filters.get('company') conditions += " AND company=%s"% frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"): if filters.get("cost_center") or filters.get("project"):
conditions += """ conditions += """
AND (cost_center='%s' AND (cost_center=%s
OR project='%s') OR project=%s)
"""% (filters.get('cost_center'), filters.get('project')) """% (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"): if filters.get("from_date"):
conditions += " AND transaction_date>=%s"% filters.get('from_date') conditions += " AND transaction_date>=%s"% filters.get('from_date')

View File

@ -117,6 +117,13 @@ def get_data():
"name": "Lead Owner Efficiency", "name": "Lead Owner Efficiency",
"doctype": "Lead", "doctype": "Lead",
"dependencies": ["Lead"] "dependencies": ["Lead"]
},
{
"type": "report",
"is_query_report": True,
"name": "Territory-wise Sales",
"doctype": "Opportunity",
"dependencies": ["Opportunity"]
} }
] ]
}, },

View File

@ -289,6 +289,10 @@ def get_data():
"name": "Job Offer", "name": "Job Offer",
"onboard": 1, "onboard": 1,
}, },
{
"type": "doctype",
"name": "Appointment Letter",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Staffing Plan", "name": "Staffing Plan",

View File

@ -58,7 +58,7 @@ class AccountsController(TransactionBase):
(is_supplier_payment and supplier.hold_type in ['All', 'Payments']): (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
if not supplier.release_date or getdate(nowdate()) <= supplier.release_date: if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
frappe.msgprint( frappe.msgprint(
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1) _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
def validate(self): def validate(self):
if not self.get('is_return'): if not self.get('is_return'):
@ -926,7 +926,7 @@ def validate_taxes_and_charges(tax):
frappe.throw( frappe.throw(
_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
elif not tax.row_id: elif not tax.row_id:
frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}").format(tax.idx, _(tax.doctype)))
elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
@ -1135,6 +1135,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
child_item.reqd_by_date = p_doctype.delivery_date child_item.reqd_by_date = p_doctype.delivery_date
child_item.uom = item.stock_uom child_item.uom = item.stock_uom
child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.warehouse = p_doctype.set_warehouse or p_doctype.items[0].warehouse
return child_item return child_item
@ -1173,7 +1174,7 @@ def check_and_delete_children(parent, data):
if parent.doctype == "Purchase Order" and flt(d.received_qty): if parent.doctype == "Purchase Order" and flt(d.received_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code)) frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
if flt(d.billed_amt): if flt(d.billed_amt):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code)) frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))

View File

@ -168,7 +168,7 @@ class BuyingController(StockController):
if item.item_code and item.qty and item.item_code in stock_and_asset_items: if item.item_code and item.qty and item.item_code in stock_and_asset_items:
item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
else flt(item.qty) / stock_and_asset_items_qty else flt(item.qty) / stock_and_asset_items_qty
if i == (last_item_idx - 1): if i == (last_item_idx - 1):
item.item_tax_amount = flt(valuation_amount_adjustment, item.item_tax_amount = flt(valuation_amount_adjustment,
self.precision("item_tax_amount", item)) self.precision("item_tax_amount", item))
@ -500,8 +500,8 @@ class BuyingController(StockController):
item_row = item_row.as_dict() item_row = item_row.as_dict()
for fieldname in field_list: for fieldname in field_list:
if flt(item_row[fieldname]) < 0: if flt(item_row[fieldname]) < 0:
frappe.throw(_("Row #{0}: {1} can not be negative for item {2}".format(item_row['idx'], frappe.throw(_("Row #{0}: {1} can not be negative for item {2}").format(item_row['idx'],
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))) frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname): def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
for d in self.get("items"): for d in self.get("items"):
@ -699,7 +699,7 @@ class BuyingController(StockController):
if delete_asset and is_auto_create_enabled: if delete_asset and is_auto_create_enabled:
# need to delete movements to delete assets otherwise throws link exists error # need to delete movements to delete assets otherwise throws link exists error
movements = frappe.db.sql( movements = frappe.db.sql(
"""SELECT asm.name """SELECT asm.name
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1) WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
for movement in movements: for movement in movements:
@ -872,9 +872,9 @@ def validate_item_type(doc, fieldname, message):
items = ", ".join([d for d in invalid_items]) items = ", ".join([d for d in invalid_items])
if len(invalid_items) > 1: if len(invalid_items) > 1:
error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
else: else:
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
frappe.throw(error_message) frappe.throw(error_message)

View File

@ -22,6 +22,7 @@
"sales_stage", "sales_stage",
"order_lost_reason", "order_lost_reason",
"mins_to_first_response", "mins_to_first_response",
"expected_closing",
"next_contact", "next_contact",
"contact_by", "contact_by",
"contact_date", "contact_date",
@ -156,6 +157,11 @@
"label": "Mins to first response", "label": "Mins to first response",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "expected_closing",
"fieldtype": "Date",
"label": "Expected Closing Date"
},
{ {
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "contact_by", "collapsible_depends_on": "contact_by",

View File

@ -37,7 +37,7 @@ class AssessmentPlan(Document):
for d in self.assessment_criteria: for d in self.assessment_criteria:
max_score += d.maximum_score max_score += d.maximum_score
if self.maximum_assessment_score != max_score: if self.maximum_assessment_score != max_score:
frappe.throw(_("Sum of Scores of Assessment Criteria needs to be {0}.".format(self.maximum_assessment_score))) frappe.throw(_("Sum of Scores of Assessment Criteria needs to be {0}.").format(self.maximum_assessment_score))
def validate_assessment_criteria(self): def validate_assessment_criteria(self):
assessment_criteria_list = frappe.db.sql_list(''' select apc.assessment_criteria assessment_criteria_list = frappe.db.sql_list(''' select apc.assessment_criteria

View File

@ -41,7 +41,7 @@ class AssessmentResult(Document):
assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]), assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]),
"student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)}) "student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)})
if assessment_result: if assessment_result:
frappe.throw(_("Assessment Result record {0} already exists.".format(getlink("Assessment Result",assessment_result[0].name)))) frappe.throw(_("Assessment Result record {0} already exists.").format(getlink("Assessment Result",assessment_result[0].name)))

View File

@ -16,4 +16,4 @@ class CourseActivity(Document):
if frappe.db.exists("Course Enrollment", self.enrollment): if frappe.db.exists("Course Enrollment", self.enrollment):
return True return True
else: else:
frappe.throw(_("Course Enrollment {0} does not exists".format(self.enrollment))) frappe.throw(_("Course Enrollment {0} does not exists").format(self.enrollment))

View File

@ -13,7 +13,7 @@ class GradingScale(Document):
thresholds = [] thresholds = []
for d in self.intervals: for d in self.intervals:
if d.threshold in thresholds: if d.threshold in thresholds:
frappe.throw(_("Treshold {0}% appears more than once".format(d.threshold))) frappe.throw(_("Treshold {0}% appears more than once").format(d.threshold))
else: else:
thresholds.append(cint(d.threshold)) thresholds.append(cint(d.threshold))
if 0 not in thresholds: if 0 not in thresholds:

View File

@ -38,7 +38,7 @@ class Question(Document):
options = self.options options = self.options
answers = [item.name for item in options if item.is_correct == True] answers = [item.name for item in options if item.is_correct == True]
if len(answers) == 0: if len(answers) == 0:
frappe.throw(_("No correct answer is set for {0}".format(self.name))) frappe.throw(_("No correct answer is set for {0}").format(self.name))
return None return None
elif len(answers) == 1: elif len(answers) == 1:
return answers[0] return answers[0]

View File

@ -34,15 +34,15 @@ class StudentGroup(Document):
students = [d.student for d in program_enrollment] if program_enrollment else [] students = [d.student for d in program_enrollment] if program_enrollment else []
for d in self.students: for d in self.students:
if not frappe.db.get_value("Student", d.student, "enabled") and d.active and not self.disabled: if not frappe.db.get_value("Student", d.student, "enabled") and d.active and not self.disabled:
frappe.throw(_("{0} - {1} is inactive student".format(d.group_roll_number, d.student_name))) frappe.throw(_("{0} - {1} is inactive student").format(d.group_roll_number, d.student_name))
if (self.group_based_on == "Batch") and cint(frappe.defaults.get_defaults().validate_batch)\ if (self.group_based_on == "Batch") and cint(frappe.defaults.get_defaults().validate_batch)\
and d.student not in students: and d.student not in students:
frappe.throw(_("{0} - {1} is not enrolled in the Batch {2}".format(d.group_roll_number, d.student_name, self.batch))) frappe.throw(_("{0} - {1} is not enrolled in the Batch {2}").format(d.group_roll_number, d.student_name, self.batch))
if (self.group_based_on == "Course") and cint(frappe.defaults.get_defaults().validate_course)\ if (self.group_based_on == "Course") and cint(frappe.defaults.get_defaults().validate_course)\
and (d.student not in students): and (d.student not in students):
frappe.throw(_("{0} - {1} is not enrolled in the Course {2}".format(d.group_roll_number, d.student_name, self.course))) frappe.throw(_("{0} - {1} is not enrolled in the Course {2}").format(d.group_roll_number, d.student_name, self.course))
def validate_and_set_child_table_fields(self): def validate_and_set_child_table_fields(self):
roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number] roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number]
@ -55,7 +55,7 @@ class StudentGroup(Document):
max_roll_no += 1 max_roll_no += 1
d.group_roll_number = max_roll_no d.group_roll_number = max_roll_no
if d.group_roll_number in roll_no_list: if d.group_roll_number in roll_no_list:
frappe.throw(_("Duplicate roll number for student {0}".format(d.student_name))) frappe.throw(_("Duplicate roll number for student {0}").format(d.student_name))
else: else:
roll_no_list.append(d.group_roll_number) roll_no_list.append(d.group_roll_number)
@ -77,7 +77,7 @@ def get_students(academic_year, group_based_on, academic_term=None, program=None
return [] return []
def get_program_enrollment(academic_year, academic_term=None, program=None, batch=None, student_category=None, course=None): def get_program_enrollment(academic_year, academic_term=None, program=None, batch=None, student_category=None, course=None):
condition1 = " " condition1 = " "
condition2 = " " condition2 = " "
if academic_term: if academic_term:
@ -93,9 +93,9 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc
condition2 = ", `tabProgram Enrollment Course` pec" condition2 = ", `tabProgram Enrollment Course` pec"
return frappe.db.sql(''' return frappe.db.sql('''
select select
pe.student, pe.student_name pe.student, pe.student_name
from from
`tabProgram Enrollment` pe {condition2} `tabProgram Enrollment` pe {condition2}
where where
pe.academic_year = %(academic_year)s {condition1} pe.academic_year = %(academic_year)s {condition1}

View File

@ -74,4 +74,4 @@ class StudentGroupCreationTool(Document):
student_group.append('students', student) student_group.append('students', student)
student_group.save() student_group.save()
frappe.msgprint(_("{0} Student Groups created.".format(l))) frappe.msgprint(_("{0} Student Groups created.").format(l))

View File

@ -185,7 +185,7 @@ def add_activity(course, content_type, content, program):
student = get_current_student() student = get_current_student()
if not student: if not student:
return frappe.throw(_("Student with email {0} does not exist".format(frappe.session.user)), frappe.DoesNotExistError) return frappe.throw(_("Student with email {0} does not exist").format(frappe.session.user), frappe.DoesNotExistError)
enrollment = get_or_create_course_enrollment(course, program) enrollment = get_or_create_course_enrollment(course, program)
if content_type == 'Quiz': if content_type == 'Quiz':
@ -220,7 +220,7 @@ def get_quiz(quiz_name, course):
quiz = frappe.get_doc("Quiz", quiz_name) quiz = frappe.get_doc("Quiz", quiz_name)
questions = quiz.get_questions() questions = quiz.get_questions()
except: except:
frappe.throw(_("Quiz {0} does not exist".format(quiz_name))) frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
return None return None
questions = [{ questions = [{
@ -347,7 +347,7 @@ def get_or_create_course_enrollment(course, program):
if not course_enrollment: if not course_enrollment:
program_enrollment = get_enrollment('program', program, student.name) program_enrollment = get_enrollment('program', program, student.name)
if not program_enrollment: if not program_enrollment:
frappe.throw(_("You are not enrolled in program {0}".format(program))) frappe.throw(_("You are not enrolled in program {0}").format(program))
return return
return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name))
else: else:

View File

@ -259,6 +259,6 @@ def get_tax_account_head(tax):
{"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account") {"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")
if not tax_account: if not tax_account:
frappe.throw(_("Tax Account not specified for Shopify Tax {0}".format(tax.get("title")))) frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
return tax_account return tax_account

View File

@ -63,7 +63,7 @@ def add_bank_accounts(response, bank, company):
default_gl_account = get_default_bank_cash_account(company, "Bank") default_gl_account = get_default_bank_cash_account(company, "Bank")
if not default_gl_account: if not default_gl_account:
frappe.throw(_("Please setup a default bank account for company {0}".format(company))) frappe.throw(_("Please setup a default bank account for company {0}").format(company))
for account in response["accounts"]: for account in response["accounts"]:
acc_type = frappe.db.get_value("Account Type", account["type"]) acc_type = frappe.db.get_value("Account Type", account["type"])

View File

@ -102,7 +102,7 @@ def invoice_appointment(appointment_doc):
sales_invoice.save(ignore_permissions=True) sales_invoice.save(ignore_permissions=True)
sales_invoice.submit() sales_invoice.submit()
frappe.msgprint(_("Sales Invoice {0} created as paid".format(sales_invoice.name)), alert=True) frappe.msgprint(_("Sales Invoice {0} created as paid").format(sales_invoice.name), alert=True)
def appointment_cancel(appointment_id): def appointment_cancel(appointment_id):
appointment = frappe.get_doc("Patient Appointment", appointment_id) appointment = frappe.get_doc("Patient Appointment", appointment_id)
@ -111,7 +111,7 @@ def appointment_cancel(appointment_id):
sales_invoice = exists_sales_invoice(appointment) sales_invoice = exists_sales_invoice(appointment)
if sales_invoice and cancel_sales_invoice(sales_invoice): if sales_invoice and cancel_sales_invoice(sales_invoice):
frappe.msgprint( frappe.msgprint(
_("Appointment {0} and Sales Invoice {1} cancelled".format(appointment.name, sales_invoice.name)) _("Appointment {0} and Sales Invoice {1} cancelled").format(appointment.name, sales_invoice.name)
) )
else: else:
validity = validity_exists(appointment.practitioner, appointment.patient) validity = validity_exists(appointment.practitioner, appointment.patient)
@ -121,7 +121,7 @@ def appointment_cancel(appointment_id):
visited = fee_validity.visited - 1 visited = fee_validity.visited - 1
frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited) frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
frappe.msgprint( frappe.msgprint(
_("Appointment cancelled, Please review and cancel the invoice {0}".format(fee_validity.ref_invoice)) _("Appointment cancelled, Please review and cancel the invoice {0}").format(fee_validity.ref_invoice)
) )
else: else:
frappe.msgprint(_("Appointment cancelled")) frappe.msgprint(_("Appointment cancelled"))
@ -203,7 +203,7 @@ def get_availability_data(date, practitioner):
if employee: if employee:
# Check if it is Holiday # Check if it is Holiday
if is_holiday(employee, date): if is_holiday(employee, date):
frappe.throw(_("{0} is a company holiday".format(date))) frappe.throw(_("{0} is a company holiday").format(date))
# Check if He/She on Leave # Check if He/She on Leave
leave_record = frappe.db.sql("""select half_day from `tabLeave Application` leave_record = frappe.db.sql("""select half_day from `tabLeave Application`
@ -221,7 +221,7 @@ def get_availability_data(date, practitioner):
if schedule.schedule: if schedule.schedule:
practitioner_schedule = frappe.get_doc("Practitioner Schedule", schedule.schedule) practitioner_schedule = frappe.get_doc("Practitioner Schedule", schedule.schedule)
else: else:
frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master".format(practitioner))) frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master").format(practitioner))
if practitioner_schedule: if practitioner_schedule:
available_slots = [] available_slots = []
@ -259,7 +259,7 @@ def get_availability_data(date, practitioner):
"avail_slot":available_slots, 'appointments': appointments}) "avail_slot":available_slots, 'appointments': appointments})
else: else:
frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master".format(practitioner))) frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master").format(practitioner))
if not available_slots and not slot_details: if not available_slots and not slot_details:
# TODO: return available slots in nearby dates # TODO: return available slots in nearby dates

View File

@ -32,8 +32,8 @@ class HotelRoomReservation(Document):
+ d.qty + self.rooms_booked.get(d.item) + d.qty + self.rooms_booked.get(d.item)
total_rooms = self.get_total_rooms(d.item) total_rooms = self.get_total_rooms(d.item)
if total_rooms < rooms_booked: if total_rooms < rooms_booked:
frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}".format(d.item, frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}").format(d.item,
frappe.format(day, dict(fieldtype="Date")))), exc=HotelRoomUnavailableError) frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomUnavailableError)
self.rooms_booked[d.item] += rooms_booked self.rooms_booked[d.item] += rooms_booked
@ -74,8 +74,8 @@ class HotelRoomReservation(Document):
net_rate += day_rate[0][0] net_rate += day_rate[0][0]
else: else:
frappe.throw( frappe.throw(
_("Please set Hotel Room Rate on {}".format( _("Please set Hotel Room Rate on {}").format(
frappe.format(day, dict(fieldtype="Date")))), exc=HotelRoomPricingNotSetError) frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomPricingNotSetError)
d.rate = net_rate d.rate = net_rate
d.amount = net_rate * flt(d.qty) d.amount = net_rate * flt(d.qty)
self.net_total += d.amount self.net_total += d.amount

View File

@ -0,0 +1,30 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Appointment Letter', {
appointment_letter_template: function(frm){
if (frm.doc.appointment_letter_template){
frappe.call({
method: 'erpnext.hr.doctype.appointment_letter.appointment_letter.get_appointment_letter_details',
args : {
template : frm.doc.appointment_letter_template
},
callback: function(r){
if(r.message){
let message_body = r.message;
frm.set_value("introduction", message_body[0].introduction);
frm.set_value("closing_notes", message_body[0].closing_notes);
frm.doc.terms = []
for (var i in message_body[1].description){
frm.add_child("terms");
frm.fields_dict.terms.get_value()[i].title = message_body[1].description[i].title;
frm.fields_dict.terms.get_value()[i].description = message_body[1].description[i].description;
}
frm.refresh();
}
}
});
}
},
});

View File

@ -0,0 +1,124 @@
{
"actions": [],
"autoname": "HR-APP-LETTER-.#####",
"creation": "2019-12-26 12:35:49.574828",
"default_print_format": "Standard Appointment Letter",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"job_applicant",
"applicant_name",
"column_break_3",
"company",
"appointment_date",
"appointment_letter_template",
"body_section",
"introduction",
"terms",
"closing_notes"
],
"fields": [
{
"fetch_from": "job_applicant.applicant_name",
"fieldname": "applicant_name",
"fieldtype": "Data",
"in_global_search": 1,
"in_list_view": 1,
"label": "Applicant Name",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "appointment_date",
"fieldtype": "Date",
"label": "Appointment Date",
"reqd": 1
},
{
"fieldname": "appointment_letter_template",
"fieldtype": "Link",
"label": "Appointment Letter Template",
"options": "Appointment Letter Template",
"reqd": 1
},
{
"fetch_from": "appointment_letter_template.introduction",
"fieldname": "introduction",
"fieldtype": "Long Text",
"label": "Introduction",
"reqd": 1
},
{
"fieldname": "body_section",
"fieldtype": "Section Break",
"label": "Body"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "job_applicant",
"fieldtype": "Link",
"label": "Job Applicant",
"options": "Job Applicant",
"reqd": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "closing_notes",
"fieldtype": "Text",
"label": "Closing Notes"
},
{
"fieldname": "terms",
"fieldtype": "Table",
"label": "Terms",
"options": "Appointment Letter content",
"reqd": 1
}
],
"links": [],
"modified": "2020-01-21 17:30:36.334395",
"modified_by": "Administrator",
"module": "HR",
"name": "Appointment Letter",
"name_case": "Title Case",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, 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 AppointmentLetter(Document):
pass
@frappe.whitelist()
def get_appointment_letter_details(template):
body = []
intro= frappe.get_list("Appointment Letter Template",
fields = ['introduction', 'closing_notes'],
filters={'name': template
})[0]
content = frappe.get_list("Appointment Letter content",
fields = ['title', 'description'],
filters={'parent': template
})
body.append(intro)
body.append({'description': content})
return body

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestAppointmentLetter(unittest.TestCase):
pass

View File

@ -0,0 +1,39 @@
{
"actions": [],
"creation": "2019-12-26 12:22:16.575767",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "description",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Description",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2019-12-26 12:24:09.824084",
"modified_by": "Administrator",
"module": "HR",
"name": "Appointment Letter content",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, 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 AppointmentLettercontent(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Appointment Letter Template', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,69 @@
{
"actions": [],
"autoname": "HR-APP-LETTER-TEMP-.#####",
"creation": "2019-12-26 12:20:14.219578",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"introduction",
"terms",
"closing_notes"
],
"fields": [
{
"fieldname": "introduction",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Introduction",
"reqd": 1
},
{
"fieldname": "closing_notes",
"fieldtype": "Text",
"label": "Closing Notes"
},
{
"fieldname": "terms",
"fieldtype": "Table",
"label": "Terms",
"options": "Appointment Letter content",
"reqd": 1
}
],
"links": [],
"modified": "2020-01-21 17:00:46.779420",
"modified_by": "Administrator",
"module": "HR",
"name": "Appointment Letter Template",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, 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 AppointmentLetterTemplate(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestAppointmentLetterTemplate(unittest.TestCase):
pass

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-01-10 16:34:13", "creation": "2013-01-10 16:34:13",
@ -63,6 +64,14 @@
"oldfieldname": "employee_name", "oldfieldname": "employee_name",
"oldfieldtype": "Data" "oldfieldtype": "Data"
}, },
{
"depends_on": "working_hours",
"fieldname": "working_hours",
"fieldtype": "Float",
"label": "Working Hours",
"precision": "1",
"read_only": 1
},
{ {
"default": "Present", "default": "Present",
"fieldname": "status", "fieldname": "status",
@ -72,7 +81,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nPresent\nAbsent\nOn Leave\nHalf Day", "options": "\nPresent\nAbsent\nOn Leave\nHalf Day\nWork From Home",
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
@ -126,6 +135,12 @@
"options": "Department", "options": "Department",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "shift",
"fieldtype": "Link",
"label": "Shift",
"options": "Shift Type"
},
{ {
"fieldname": "attendance_request", "fieldname": "attendance_request",
"fieldtype": "Link", "fieldtype": "Link",
@ -143,20 +158,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{
"depends_on": "working_hours",
"fieldname": "working_hours",
"fieldtype": "Float",
"label": "Working Hours",
"precision": "1",
"read_only": 1
},
{
"fieldname": "shift",
"fieldtype": "Link",
"label": "Shift",
"options": "Shift Type"
},
{ {
"default": "0", "default": "0",
"fieldname": "late_entry", "fieldname": "late_entry",
@ -173,7 +174,8 @@
"icon": "fa fa-ok", "icon": "fa fa-ok",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-07-29 20:35:40.845422", "links": [],
"modified": "2020-01-27 20:25:29.572281",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Attendance", "name": "Attendance",

View File

@ -52,7 +52,7 @@ class Attendance(Document):
def validate(self): def validate(self):
from erpnext.controllers.status_updater import validate_status from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day"]) validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
self.validate_attendance_date() self.validate_attendance_date()
self.validate_duplicate_record() self.validate_duplicate_record()
self.check_leave_record() self.check_leave_record()

View File

@ -1,7 +1,13 @@
frappe.listview_settings['Attendance'] = { frappe.listview_settings['Attendance'] = {
add_fields: ["status", "attendance_date"], add_fields: ["status", "attendance_date"],
get_indicator: function(doc) { get_indicator: function (doc) {
return [__(doc.status), doc.status=="Present" ? "green" : "darkgrey", "status,=," + doc.status]; if (["Present", "Work From Home"].includes(doc.status)) {
return [__(doc.status), "green", "status,=," + doc.status];
} else if (["Absent", "On Leave"].includes(doc.status)) {
return [__(doc.status), "red", "status,=," + doc.status];
} else if (doc.status == "Half Day") {
return [__(doc.status), "orange", "status,=," + doc.status];
}
}, },
onload: function(list_view) { onload: function(list_view) {
let me = this; let me = this;
@ -44,7 +50,7 @@ frappe.listview_settings['Attendance'] = {
label: __("Status"), label: __("Status"),
fieldtype: "Select", fieldtype: "Select",
fieldname: "status", fieldname: "status",
options: ["Present", "Absent", "Half Day"], options: ["Present", "Absent", "Half Day", "Work From Home"],
hidden:1, hidden:1,
reqd: 1, reqd: 1,
@ -102,5 +108,4 @@ frappe.listview_settings['Attendance'] = {
}); });
}); });
} }
}; };

View File

@ -38,6 +38,8 @@ class AttendanceRequest(Document):
attendance.employee_name = self.employee_name attendance.employee_name = self.employee_name
if self.half_day and date_diff(getdate(self.half_day_date), getdate(attendance_date)) == 0: if self.half_day and date_diff(getdate(self.half_day_date), getdate(attendance_date)) == 0:
attendance.status = "Half Day" attendance.status = "Half Day"
elif self.reason == "Work From Home":
attendance.status = "Work From Home"
else: else:
attendance.status = "Present" attendance.status = "Present"
attendance.attendance_date = attendance_date attendance.attendance_date = attendance_date

View File

@ -13,7 +13,28 @@ class TestAttendanceRequest(unittest.TestCase):
for doctype in ["Attendance Request", "Attendance"]: for doctype in ["Attendance Request", "Attendance"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
def test_attendance_request(self): def test_on_duty_attendance_request(self):
today = nowdate()
employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request")
attendance_request.employee = employee.name
attendance_request.from_date = date(date.today().year, 1, 1)
attendance_request.to_date = date(date.today().year, 1, 2)
attendance_request.reason = "On Duty"
attendance_request.company = "_Test Company"
attendance_request.insert()
attendance_request.submit()
attendance = frappe.get_doc('Attendance', {
'employee': employee.name,
'attendance_date': date(date.today().year, 1, 1),
'docstatus': 1
})
self.assertEqual(attendance.status, 'Present')
attendance_request.cancel()
attendance.reload()
self.assertEqual(attendance.docstatus, 2)
def test_work_from_home_attendance_request(self):
today = nowdate() today = nowdate()
employee = get_employee() employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request") attendance_request = frappe.new_doc("Attendance Request")
@ -29,7 +50,7 @@ class TestAttendanceRequest(unittest.TestCase):
'attendance_date': date(date.today().year, 1, 1), 'attendance_date': date(date.today().year, 1, 1),
'docstatus': 1 'docstatus': 1
}) })
self.assertEqual(attendance.status, 'Present') self.assertEqual(attendance.status, 'Work From Home')
attendance_request.cancel() attendance_request.cancel()
attendance.reload() attendance.reload()
self.assertEqual(attendance.docstatus, 2) self.assertEqual(attendance.docstatus, 2)

View File

@ -97,9 +97,9 @@ class DailyWorkSummary(Document):
return dict(replies=replies, return dict(replies=replies,
original_message=dws_group.message, original_message=dws_group.message,
title=_('Work Summary for {0}'.format( title=_('Work Summary for {0}').format(
global_date_format(self.creation) global_date_format(self.creation)
)), ),
did_not_reply=', '.join(did_not_reply) or '', did_not_reply=', '.join(did_not_reply) or '',
did_not_reply_title=_('No replies from')) did_not_reply_title=_('No replies from'))

View File

@ -124,8 +124,10 @@ erpnext.EmployeeSelector = Class.extend({
var mark_employee_toolbar = $('<div class="col-sm-12 bottom-toolbar">\ var mark_employee_toolbar = $('<div class="col-sm-12 bottom-toolbar">\
<button class="btn btn-primary btn-mark-present btn-xs"></button>\ <button class="btn btn-primary btn-mark-present btn-xs"></button>\
<button class="btn btn-default btn-mark-absent btn-xs"></button>\ <button class="btn btn-primary btn-mark-work-from-home btn-xs"></button>\
<button class="btn btn-default btn-mark-half-day btn-xs"></button></div>') <button class="btn btn-warning btn-mark-half-day btn-xs"></button>\
<button class="btn btn-danger btn-mark-absent btn-xs"></button>\
</div>');
employee_toolbar.find(".btn-add") employee_toolbar.find(".btn-add")
.html(__('Check all')) .html(__('Check all'))
@ -224,6 +226,31 @@ erpnext.EmployeeSelector = Class.extend({
}); });
mark_employee_toolbar.find(".btn-mark-work-from-home")
.html(__('Mark Work From Home'))
.on("click", function() {
var employee_work_from_home = [];
$(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
if($(check).is(":checked")) {
employee_work_from_home.push(employee[i]);
}
});
frappe.call({
method: "erpnext.hr.doctype.employee_attendance_tool.employee_attendance_tool.mark_employee_attendance",
args:{
"employee_list":employee_work_from_home,
"status":"Work From Home",
"date":frm.doc.date,
"company":frm.doc.company
},
callback: function(r) {
erpnext.employee_attendance_tool.load_employees(frm);
}
});
});
var row; var row;
$.each(employee, function(i, m) { $.each(employee, function(i, m) {
if (i===0 || (i % 4) === 0) { if (i===0 || (i % 4) === 0) {

View File

@ -84,7 +84,7 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct):
pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"])
except TypeError: except TypeError:
# show the error in tests? # show the error in tests?
frappe.throw(_("Unable to find Salary Component {0}".format(sal_struct_row.salary_component))) frappe.throw(_("Unable to find Salary Component {0}").format(sal_struct_row.salary_component))
if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1: if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1:
total_pro_rata_max += max_benefit_amount total_pro_rata_max += max_benefit_amount
if total_pro_rata_max > 0: if total_pro_rata_max > 0:

View File

@ -104,11 +104,16 @@ frappe.ui.form.on("Leave Application", {
}, },
half_day: function(frm) { half_day: function(frm) {
if (frm.doc.from_date == frm.doc.to_date) { if (frm.doc.half_day) {
frm.set_value("half_day_date", frm.doc.from_date); if (frm.doc.from_date == frm.doc.to_date) {
frm.set_value("half_day_date", frm.doc.from_date);
}
else {
frm.trigger("half_day_datepicker");
}
} }
else { else {
frm.trigger("half_day_datepicker"); frm.set_value("half_day_date", "");
} }
frm.trigger("calculate_total_days"); frm.trigger("calculate_total_days");
}, },

View File

@ -364,11 +364,11 @@ class SalarySlip(TransactionBase):
return amount return amount
except NameError as err: except NameError as err:
frappe.throw(_("Name error: {0}".format(err))) frappe.throw(_("Name error: {0}").format(err))
except SyntaxError as err: except SyntaxError as err:
frappe.throw(_("Syntax error in formula or condition: {0}".format(err))) frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
except Exception as e: except Exception as e:
frappe.throw(_("Error in formula or condition: {0}".format(e))) frappe.throw(_("Error in formula or condition: {0}").format(e))
raise raise
def add_employee_benefits(self, payroll_period): def add_employee_benefits(self, payroll_period):
@ -705,11 +705,11 @@ class SalarySlip(TransactionBase):
if condition: if condition:
return frappe.safe_eval(condition, self.whitelisted_globals, data) return frappe.safe_eval(condition, self.whitelisted_globals, data)
except NameError as err: except NameError as err:
frappe.throw(_("Name error: {0}".format(err))) frappe.throw(_("Name error: {0}").format(err))
except SyntaxError as err: except SyntaxError as err:
frappe.throw(_("Syntax error in condition: {0}".format(err))) frappe.throw(_("Syntax error in condition: {0}").format(err))
except Exception as e: except Exception as e:
frappe.throw(_("Error in formula or condition: {0}".format(e))) frappe.throw(_("Error in formula or condition: {0}").format(e))
raise raise
def get_salary_slip_row(self, salary_component): def get_salary_slip_row(self, salary_component):

View File

@ -57,8 +57,8 @@ class StaffingPlan(Document):
and sp.to_date >= %s and sp.from_date <= %s and sp.company = %s and sp.to_date >= %s and sp.from_date <= %s and sp.company = %s
""", (staffing_plan_detail.designation, self.from_date, self.to_date, self.company)) """, (staffing_plan_detail.designation, self.from_date, self.to_date, self.company))
if overlap and overlap [0][0]: if overlap and overlap [0][0]:
frappe.throw(_("Staffing Plan {0} already exist for designation {1}" frappe.throw(_("Staffing Plan {0} already exist for designation {1}")
.format(overlap[0][0], staffing_plan_detail.designation))) .format(overlap[0][0], staffing_plan_detail.designation))
def validate_with_parent_plan(self, staffing_plan_detail): def validate_with_parent_plan(self, staffing_plan_detail):
if not frappe.get_cached_value('Company', self.company, "parent_company"): if not frappe.get_cached_value('Company', self.company, "parent_company"):

View File

@ -0,0 +1,38 @@
{%- from "templates/print_formats/standard_macros.html" import add_header -%}
<div class="text-center" style="margin-bottom: 10%;"><h3>Appointment Letter</h3></div>
<div class ='row' style="margin-bottom: 5%;">
<div class = "col-sm-6">
<b>{{ doc.applicant_name }},</b>
</div>
<div class="col-sm-6">
<span style = "float: right;"><b> Date: </b>{{ doc.appointment_date }}</span>
</div>
</div>
<div style="margin-bottom: 5%;">
{{ doc.introduction }}
</div>
<div style="margin-bottom: 5%;">
<ul>
{% for content in doc.terms %}
<li style="padding-bottom: 3%;">
<span>
<span><b>{{ content.title }}: </b></span> {{ content.description }}
</span>
</li>
{% endfor %}
</ul>
</div>
<div style="margin-bottom: 5%;">
<span>Your sincerely,</span><br>
<span><b>For {{ doc.company }}</b></span>
</div>
<div style="margin-bottom: 5%;">
<span>{{ doc.closing_notes }}</span>
</div>
<div>
<span><b>________________</b></span><br>
<span><b>{{ doc.applicant_name }}</b></span>
</div>

View File

@ -0,0 +1,23 @@
{
"align_labels_right": 0,
"creation": "2019-12-26 15:22:44.200332",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Appointment Letter",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"idx": 0,
"line_breaks": 0,
"modified": "2020-01-21 17:24:16.705082",
"modified_by": "Administrator",
"module": "HR",
"name": "Standard Appointment Letter",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

View File

@ -37,13 +37,22 @@ def execute(filters=None):
total_p = total_a = total_l = 0.0 total_p = total_a = total_l = 0.0
for day in range(filters["total_days_in_month"]): for day in range(filters["total_days_in_month"]):
status = att_map.get(emp).get(day + 1, "None") status = att_map.get(emp).get(day + 1)
status_map = {"Present": "P", "Absent": "A", "Half Day": "HD", "On Leave": "L", "None": "", "Holiday":"<b>H</b>"} status_map = {
if status == "None" and holiday_map: "Absent": "A",
"Half Day": "HD",
"Holiday":"<b>H</b>",
"On Leave": "L",
"Present": "P",
"Work From Home": "WFH"
}
if status is None and holiday_map:
emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list
if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list]: if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list]:
status = "Holiday" status = "Holiday"
row.append(status_map[status])
row.append(status_map.get(status, ""))
if status == "Present": if status == "Present":
total_p += 1 total_p += 1
@ -66,7 +75,7 @@ def execute(filters=None):
leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\ leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1) where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \ time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \ late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters) early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
@ -85,7 +94,7 @@ def execute(filters=None):
row.append(leaves[d]) row.append(leaves[d])
else: else:
row.append("0.0") row.append("0.0")
row.extend([time_default_counts[0][0],time_default_counts[0][1]]) row.extend([time_default_counts[0][0],time_default_counts[0][1]])
data.append(row) data.append(row)
return columns, data return columns, data

View File

@ -54,7 +54,7 @@ class MaintenanceSchedule(TransactionBase):
email_map[d.sales_person] = sp.get_email_id() email_map[d.sales_person] = sp.get_email_id()
except frappe.ValidationError: except frappe.ValidationError:
no_email_sp.append(d.sales_person) no_email_sp.append(d.sales_person)
if no_email_sp: if no_email_sp:
frappe.msgprint( frappe.msgprint(
frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
@ -66,7 +66,7 @@ class MaintenanceSchedule(TransactionBase):
parent=%s""", (d.sales_person, d.item_code, self.name), as_dict=1) parent=%s""", (d.sales_person, d.item_code, self.name), as_dict=1)
for key in scheduled_date: for key in scheduled_date:
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer) description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
frappe.get_doc({ frappe.get_doc({
"doctype": "Event", "doctype": "Event",
"owner": email_map.get(d.sales_person, self.owner), "owner": email_map.get(d.sales_person, self.owner),
@ -146,11 +146,11 @@ class MaintenanceSchedule(TransactionBase):
if not d.item_code: if not d.item_code:
throw(_("Please select item code")) throw(_("Please select item code"))
elif not d.start_date or not d.end_date: elif not d.start_date or not d.end_date:
throw(_("Please select Start Date and End Date for Item {0}".format(d.item_code))) throw(_("Please select Start Date and End Date for Item {0}").format(d.item_code))
elif not d.no_of_visits: elif not d.no_of_visits:
throw(_("Please mention no of visits required")) throw(_("Please mention no of visits required"))
elif not d.sales_person: elif not d.sales_person:
throw(_("Please select a Sales Person for item: {0}".format(d.item_name))) throw(_("Please select a Sales Person for item: {0}").format(d.item_name))
if getdate(d.start_date) >= getdate(d.end_date): if getdate(d.start_date) >= getdate(d.end_date):
throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) throw(_("Start date should be less than end date for Item {0}").format(d.item_code))
@ -193,7 +193,7 @@ class MaintenanceSchedule(TransactionBase):
if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date): if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date):
throw(_("Serial No {0} is under maintenance contract upto {1}") throw(_("Serial No {0} is under maintenance contract upto {1}")
.format(serial_no, sr_details.amc_start_date)) .format(serial_no, sr_details.amc_expiry_date))
if not sr_details.warehouse and sr_details.delivery_date and \ if not sr_details.warehouse and sr_details.delivery_date and \
getdate(sr_details.delivery_date) >= getdate(amc_start_date): getdate(sr_details.delivery_date) >= getdate(amc_start_date):

View File

@ -8,7 +8,7 @@ import datetime
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
get_time, add_to_date, time_diff, add_days, get_datetime_str) get_time, add_to_date, time_diff, add_days, get_datetime_str)
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
@ -43,7 +43,7 @@ class JobCard(Document):
def get_overlap_for(self, args, check_next_available_slot=False): def get_overlap_for(self, args, check_next_available_slot=False):
production_capacity = 1 production_capacity = 1
if self.workstation: if self.workstation:
production_capacity = frappe.get_cached_value("Workstation", production_capacity = frappe.get_cached_value("Workstation",
self.workstation, 'production_capacity') or 1 self.workstation, 'production_capacity') or 1
@ -195,8 +195,8 @@ class JobCard(Document):
frappe.throw(_("Total completed qty must be greater than zero")) frappe.throw(_("Total completed qty must be greater than zero"))
if self.total_completed_qty != self.for_quantity: if self.total_completed_qty != self.for_quantity:
frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})" frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})")
.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))) .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))
def update_work_order(self): def update_work_order(self):
if not self.work_order: if not self.work_order:
@ -372,7 +372,7 @@ def get_job_details(start, end, filters=None):
conditions = get_filters_cond("Job Card", filters, []) conditions = get_filters_cond("Job Card", filters, [])
job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order, job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
`tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), `tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
min(`tabJob Card Time Log`.from_time) as from_time, min(`tabJob Card Time Log`.from_time) as from_time,
max(`tabJob Card Time Log`.to_time) as to_time max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log` FROM `tabJob Card` , `tabJob Card Time Log`

View File

@ -22,7 +22,7 @@ class ProductionPlan(Document):
def validate_data(self): def validate_data(self):
for d in self.get('po_items'): for d in self.get('po_items'):
if not d.bom_no: if not d.bom_no:
frappe.throw(_("Please select BOM for Item in Row {0}".format(d.idx))) frappe.throw(_("Please select BOM for Item in Row {0}").format(d.idx))
else: else:
validate_bom_no(d.item_code, d.bom_no) validate_bom_no(d.item_code, d.bom_no)

View File

@ -121,6 +121,15 @@ frappe.ui.form.on("Work Order", {
} }
}, },
source_warehouse: function(frm) {
if (frm.doc.source_warehouse) {
frm.doc.required_items.forEach(d => {
frappe.model.set_value(d.doctype, d.name,
"source_warehouse", frm.doc.source_warehouse);
});
}
},
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
erpnext.work_order.set_custom_buttons(frm); erpnext.work_order.set_custom_buttons(frm);

View File

@ -29,9 +29,10 @@
"from_wip_warehouse", "from_wip_warehouse",
"update_consumed_material_cost_in_project", "update_consumed_material_cost_in_project",
"warehouses", "warehouses",
"source_warehouse",
"wip_warehouse", "wip_warehouse",
"fg_warehouse",
"column_break_12", "column_break_12",
"fg_warehouse",
"scrap_warehouse", "scrap_warehouse",
"required_items_section", "required_items_section",
"required_items", "required_items",
@ -226,12 +227,14 @@
"options": "fa fa-building" "options": "fa fa-building"
}, },
{ {
"description": "This is a location where operations are executed.",
"fieldname": "wip_warehouse", "fieldname": "wip_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Work-in-Progress Warehouse", "label": "Work-in-Progress Warehouse",
"options": "Warehouse" "options": "Warehouse"
}, },
{ {
"description": "This is a location where final product stored.",
"fieldname": "fg_warehouse", "fieldname": "fg_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Target Warehouse", "label": "Target Warehouse",
@ -242,6 +245,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"description": "This is a location where scraped materials are stored.",
"fieldname": "scrap_warehouse", "fieldname": "scrap_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Scrap Warehouse", "label": "Scrap Warehouse",
@ -463,6 +467,13 @@
"fieldname": "update_consumed_material_cost_in_project", "fieldname": "update_consumed_material_cost_in_project",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Update Consumed Material Cost In Project" "label": "Update Consumed Material Cost In Project"
},
{
"description": "This is a location where raw materials are available.",
"fieldname": "source_warehouse",
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
} }
], ],
"icon": "fa fa-cogs", "icon": "fa fa-cogs",
@ -470,7 +481,7 @@
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-04 11:20:04.695123", "modified": "2020-01-31 12:46:23.636033",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "name": "Work Order",

View File

@ -461,7 +461,7 @@ class WorkOrder(Document):
def validate_operation_time(self): def validate_operation_time(self):
for d in self.operations: for d in self.operations:
if not d.time_in_mins > 0: if not d.time_in_mins > 0:
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation))) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self): def update_required_items(self):
''' '''

View File

@ -656,3 +656,4 @@ erpnext.patches.v12_0.set_lead_title_field
erpnext.patches.v12_0.set_permission_einvoicing erpnext.patches.v12_0.set_permission_einvoicing
erpnext.patches.v12_0.set_published_in_hub_tracked_item erpnext.patches.v12_0.set_published_in_hub_tracked_item
erpnext.patches.v12_0.set_job_offer_applicant_email erpnext.patches.v12_0.set_job_offer_applicant_email
erpnext.patches.v12_0.create_irs_1099_field_united_states

View File

@ -0,0 +1,10 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.united_states.setup import make_custom_fields
def execute():
company = frappe.get_all('Company', filters = {'country': 'United States'})
if not company:
return
make_custom_fields()

View File

@ -188,7 +188,7 @@ class Project(Document):
def send_welcome_email(self): def send_welcome_email(self):
url = get_url("/project/?name={0}".format(self.name)) url = get_url("/project/?name={0}".format(self.name))
messages = ( messages = (
_("You have been invited to collaborate on the project: {0}".format(self.name)), _("You have been invited to collaborate on the project: {0}").format(self.name),
url, url,
_("Join") _("Join")
) )

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