Merge branch 'staging-fixes' into permission-fixes

This commit is contained in:
Suraj Shetty 2018-12-18 15:41:03 +05:30 committed by GitHub
commit 7ae2d59ffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 4077 additions and 4213 deletions

2
.github/stale.yml vendored
View File

@ -1,7 +1,7 @@
# Configuration for probot-stale - https://github.com/probot/stale # Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale # Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 10 daysUntilStale: 30
# Number of days of inactivity before a stale Issue or Pull Request is closed. # Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.

View File

@ -33,7 +33,7 @@ before_script:
- cd ~/frappe-bench - cd ~/frappe-bench
- bench get-app erpnext $TRAVIS_BUILD_DIR - bench get-app erpnext $TRAVIS_BUILD_DIR
- bench use test_site - bench use test_site
- bench reinstall --yes - bench reinstall --mariadb-root-username root --mariadb-root-password travis --yes
- bench build - bench build
- bench scheduler disable - bench scheduler disable
- sed -i 's/9000/9001/g' sites/common_site_config.json - sed -i 's/9000/9001/g' sites/common_site_config.json

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '10.1.72' __version__ = '10.1.74'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -70,12 +70,14 @@ class PeriodClosingVoucher(AccountsController):
net_pl_balance += flt(acc.balance_in_company_currency) net_pl_balance += flt(acc.balance_in_company_currency)
if net_pl_balance: if net_pl_balance:
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": self.closing_account_head, "account": self.closing_account_head,
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0 "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"cost_center": cost_center
})) }))
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries

View File

@ -72,6 +72,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
"company": "_Test Company", "company": "_Test Company",
"fiscal_year": get_fiscal_year(today(), company="_Test Company")[0], "fiscal_year": get_fiscal_year(today(), company="_Test Company")[0],
"posting_date": today(), "posting_date": today(),
"cost_center": "_Test Cost Center - _TC",
"remarks": "test" "remarks": "test"
}) })
pcv.insert() pcv.insert()

View File

@ -37,11 +37,25 @@ frappe.ui.form.on('POS Profile', {
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} }; return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
}); });
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'use_pos_in_offline_mode', (r) => { frappe.db.get_value('POS Settings', 'POS Settings', 'use_pos_in_offline_mode', (r) => {
is_offline = r && cint(r.use_pos_in_offline_mode) const is_offline = r && cint(r.use_pos_in_offline_mode)
frm.toggle_display('offline_pos_section', is_offline); frm.toggle_display('offline_pos_section', is_offline);
frm.toggle_display('print_format_for_online', !is_offline); frm.toggle_display('print_format_for_online', !is_offline);
}); });
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
link_doctype: 'Company',
link_name: doc.company
}
};
});
}, },
refresh: function(frm) { refresh: function(frm) {
@ -49,11 +63,11 @@ frappe.ui.form.on('POS Profile', {
frm.trigger("toggle_display_account_head"); frm.trigger("toggle_display_account_head");
} }
}, },
company: function(frm) { company: function(frm) {
frm.trigger("toggle_display_account_head"); frm.trigger("toggle_display_account_head");
}, },
toggle_display_account_head: function(frm) { toggle_display_account_head: function(frm) {
frm.toggle_display('expense_account', frm.toggle_display('expense_account',
erpnext.is_perpetual_inventory_enabled(frm.doc.company)); erpnext.is_perpetual_inventory_enabled(frm.doc.company));

File diff suppressed because it is too large Load Diff

View File

@ -127,25 +127,26 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
'txt': '%%%s%%' % txt 'txt': '%%%s%%' % txt
} }
pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name pos_profile = frappe.db.sql("""select pf.name
from from
`tabPOS Profile` pf, `tabPOS Profile User` pfu `tabPOS Profile` pf, `tabPOS Profile User` pfu
where where
pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s
and (pf.name like %(txt)s or pf.pos_profile_name like %(txt)s) and (pf.name like %(txt)s)
and pf.disabled = 0 limit %(start)s, %(page_len)s""", args) and pf.disabled = 0 limit %(start)s, %(page_len)s""", args)
if not pos_profile: if not pos_profile:
del args['user'] del args['user']
pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name pos_profile = frappe.db.sql("""select pf.name
from from
`tabPOS Profile` pf left join `tabPOS Profile User` pfu `tabPOS Profile` pf left join `tabPOS Profile User` pfu
on on
pf.name = pfu.parent pf.name = pfu.parent
where where
ifnull(pfu.user, '') = '' and pf.company = %(company)s and ifnull(pfu.user, '') = ''
(pf.name like %(txt)s or pf.pos_profile_name like %(txt)s) and pf.company = %(company)s
and pf.name like %(txt)s
and pf.disabled = 0""", args) and pf.disabled = 0""", args)
return pos_profile return pos_profile

View File

@ -40,7 +40,6 @@ def make_pos_profile():
"expense_account": "_Test Account Cost for Goods Sold - _TC", "expense_account": "_Test Account Cost for Goods Sold - _TC",
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"name": "_Test POS Profile", "name": "_Test POS Profile",
"pos_profile_name": "_Test POS Profile",
"naming_series": "_T-POS Profile-", "naming_series": "_T-POS Profile-",
"selling_price_list": "_Test Price List", "selling_price_list": "_Test Price List",
"territory": "_Test Territory", "territory": "_Test Territory",

View File

@ -21,8 +21,6 @@ class TestPricingRule(unittest.TestCase):
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
test_record = { test_record = {
"doctype": "Pricing Rule", "doctype": "Pricing Rule",
"title": "_Test Pricing Rule", "title": "_Test Pricing Rule",
@ -93,7 +91,7 @@ class TestPricingRule(unittest.TestCase):
args.item_code = "_Test Item 2" args.item_code = "_Test Item 2"
details = get_item_details(args) details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 15) self.assertEquals(details.get("discount_percentage"), 15)
def test_pricing_rule_for_margin(self): def test_pricing_rule_for_margin(self):
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
@ -137,8 +135,8 @@ class TestPricingRule(unittest.TestCase):
"name": None "name": None
}) })
details = get_item_details(args) details = get_item_details(args)
self.assertEqual(details.get("margin_type"), "Percentage") self.assertEquals(details.get("margin_type"), "Percentage")
self.assertEqual(details.get("margin_rate_or_amount"), 10) self.assertEquals(details.get("margin_rate_or_amount"), 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
@ -253,6 +251,7 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(so.items[0].rate, 100) self.assertEqual(so.items[0].rate, 100)
def test_pricing_rule_with_margin_and_discount(self): def test_pricing_rule_with_margin_and_discount(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10, discount_percentage=10) make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10, discount_percentage=10)
si = create_sales_invoice(do_not_save=True) si = create_sales_invoice(do_not_save=True)
si.items[0].price_list_rate = 1000 si.items[0].price_list_rate = 1000
@ -260,12 +259,11 @@ class TestPricingRule(unittest.TestCase):
si.insert(ignore_permissions=True) si.insert(ignore_permissions=True)
item = si.items[0] item = si.items[0]
self.assertEqual(item.margin_rate_or_amount, 10) self.assertEquals(item.margin_rate_or_amount, 10)
self.assertEqual(item.rate_with_margin, 1100) self.assertEquals(item.rate_with_margin, 1100)
self.assertEqual(item.discount_percentage, 10) self.assertEqual(item.discount_percentage, 10)
self.assertEqual(item.discount_amount, 110) self.assertEquals(item.discount_amount, 110)
self.assertEqual(item.rate, 990) self.assertEquals(item.rate, 990)
def make_pricing_rule(**args): def make_pricing_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -295,4 +293,4 @@ def make_pricing_rule(**args):
applicable_for = doc.applicable_for.replace(' ', '_').lower() applicable_for = doc.applicable_for.replace(' ', '_').lower()
if args.get(applicable_for): if args.get(applicable_for):
doc.db_set(applicable_for, args.get(applicable_for)) doc.db_set(applicable_for, args.get(applicable_for))

View File

@ -226,7 +226,7 @@ class PurchaseInvoice(BuyingController):
item.expense_account = warehouse_account[item.warehouse]["account"] item.expense_account = warehouse_account[item.warehouse]["account"]
else: else:
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
elif not item.expense_account and for_validate: elif not item.expense_account and for_validate:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
@ -379,7 +379,7 @@ class PurchaseInvoice(BuyingController):
return gl_entries return gl_entries
def make_supplier_gl_entry(self, gl_entries): def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total # Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
@ -859,7 +859,8 @@ def make_stock_entry(source_name, target_doc=None):
"Purchase Invoice Item": { "Purchase Invoice Item": {
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"field_map": { "field_map": {
"stock_qty": "transfer_qty" "stock_qty": "transfer_qty",
"batch_no": "batch_no"
}, },
} }
}, target_doc) }, target_doc)

View File

@ -313,6 +313,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
erpnext.setup_serial_no(); erpnext.setup_serial_no();
}, },
packed_items_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no();
},
make_sales_return: function() { make_sales_return: function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return", method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return",
@ -585,7 +589,7 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query('company_address', function(doc) { frm.set_query('company_address', function(doc) {
if(!doc.company) { if(!doc.company) {
frappe.throw(_('Please set Company')); frappe.throw(__('Please set Company'));
} }
return { return {

View File

@ -131,7 +131,7 @@ class SalesInvoice(SellingController):
#validate amount in mode of payments for returned invoices for pos must be negative #validate amount in mode of payments for returned invoices for pos must be negative
if self.is_pos and self.is_return: if self.is_pos and self.is_return:
self.verify_payment_amount_is_negative() self.verify_payment_amount_is_negative()
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
validate_loyalty_points(self, self.loyalty_points) validate_loyalty_points(self, self.loyalty_points)
@ -397,7 +397,7 @@ class SalesInvoice(SellingController):
self.account_for_change_amount = pos.get('account_for_change_amount') self.account_for_change_amount = pos.get('account_for_change_amount')
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name', for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account', 'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account', 'company_address',
'write_off_account', 'write_off_cost_center', 'apply_discount_on'): 'write_off_account', 'write_off_cost_center', 'apply_discount_on'):
if (not for_validate) or (for_validate and not self.get(fieldname)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
@ -713,7 +713,7 @@ class SalesInvoice(SellingController):
return gl_entries return gl_entries
def make_customer_gl_entry(self, gl_entries): def make_customer_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total # Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total: if grand_total:
@ -1026,7 +1026,7 @@ class SalesInvoice(SellingController):
def verify_payment_amount_is_negative(self): def verify_payment_amount_is_negative(self):
for entry in self.payments: for entry in self.payments:
if entry.amount > 0: if entry.amount > 0:
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
# collection of the loyalty points, create the ledger entry for that. # collection of the loyalty points, create the ledger entry for that.
def make_loyalty_point_entry(self): def make_loyalty_point_entry(self):

View File

@ -189,7 +189,7 @@ class ShareTransfer(Document):
if (shareholder == 'from_shareholder') else self.to_folio_no; if (shareholder == 'from_shareholder') else self.to_folio_no;
doc.save() doc.save()
else: else:
if doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no): if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
frappe.throw(_('The folio numbers are not matching')) frappe.throw(_('The folio numbers are not matching'))
def autoname_folio(self, shareholder, is_company=False): def autoname_folio(self, shareholder, is_company=False):

View File

@ -79,7 +79,8 @@ class TestShareTransfer(unittest.TestCase):
} }
] ]
for d in share_transfers: for d in share_transfers:
frappe.get_doc(d).submit() st = frappe.get_doc(d)
st.submit()
def test_invalid_share_transfer(self): def test_invalid_share_transfer(self):
doc = frappe.get_doc({ doc = frappe.get_doc({

View File

@ -333,7 +333,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
def validate_due_date(posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None): def validate_due_date(posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None):
if getdate(due_date) < getdate(posting_date): if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting Date")) frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
else: else:
if not template_name: return if not template_name: return
@ -457,38 +457,65 @@ def get_timeline_data(doctype, name):
def get_dashboard_info(party_type, party): def get_dashboard_info(party_type, party):
current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)
company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name
party_account_currency = get_party_account_currency(party_type, party, company)
company_default_currency = get_default_currency() \
or frappe.get_cached_value('Company', company, 'default_currency')
if party_account_currency==company_default_currency:
total_field = "base_grand_total"
else:
total_field = "grand_total"
doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
billing_this_year = frappe.db.sql(""" companies = frappe.get_all(doctype, filters={
select sum({0}) 'docstatus': 1,
from `tab{1}` party_type.lower(): party
where {2}=%s and docstatus=1 and posting_date between %s and %s }, distinct=1, fields=['company'])
""".format(total_field, doctype, party_type.lower()),
(party, current_fiscal_year.year_start_date, current_fiscal_year.year_end_date))
total_unpaid = frappe.db.sql(""" company_wise_info = []
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
company_wise_grand_total = frappe.get_all(doctype,
filters={
'docstatus': 1,
party_type.lower(): party,
'posting_date': ('between', [current_fiscal_year.year_start_date, current_fiscal_year.year_end_date])
},
group_by="company",
fields=["company", "sum(grand_total) as grand_total", "sum(base_grand_total) as base_grand_total"]
)
company_wise_billing_this_year = frappe._dict()
for d in company_wise_grand_total:
company_wise_billing_this_year.setdefault(
d.company,{
"grand_total": d.grand_total,
"base_grand_total": d.base_grand_total
})
company_wise_total_unpaid = frappe._dict(frappe.db.sql("""
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` from `tabGL Entry`
where party_type = %s and party=%s""", (party_type, party)) where party_type = %s and party=%s
group by company""", (party_type, party)))
info = {} for d in companies:
info["billing_this_year"] = flt(billing_this_year[0][0]) if billing_this_year else 0 company_default_currency = frappe.db.get_value("Company", d.company, 'default_currency')
info["currency"] = party_account_currency party_account_currency = get_party_account_currency(party_type, party, d.company)
info["total_unpaid"] = flt(total_unpaid[0][0]) if total_unpaid else 0
if party_type == "Supplier":
info["total_unpaid"] = -1 * info["total_unpaid"]
return info if party_account_currency==company_default_currency:
billing_this_year = flt(company_wise_billing_this_year.get(d.company,{}).get("base_grand_total"))
else:
billing_this_year = flt(company_wise_billing_this_year.get(d.company,{}).get("grand_total"))
total_unpaid = flt(company_wise_total_unpaid.get(d.company))
info = {}
info["billing_this_year"] = flt(billing_this_year) if billing_this_year else 0
info["currency"] = party_account_currency
info["total_unpaid"] = flt(total_unpaid) if total_unpaid else 0
info["company"] = d.company
if party_type == "Supplier":
info["total_unpaid"] = -1 * info["total_unpaid"]
company_wise_info.append(info)
return company_wise_info
def get_party_shipping_address(doctype, name): def get_party_shipping_address(doctype, name):
""" """
@ -508,7 +535,7 @@ def get_party_shipping_address(doctype, name):
'dl.link_doctype=%s ' 'dl.link_doctype=%s '
'and dl.link_name=%s ' 'and dl.link_name=%s '
'and dl.parenttype="Address" ' 'and dl.parenttype="Address" '
'and ' 'and ifnull(ta.disabled, 0) = 0 and'
'(ta.address_type="Shipping" or ta.is_shipping_address=1) ' '(ta.address_type="Shipping" or ta.is_shipping_address=1) '
'order by ta.is_shipping_address desc, ta.address_type desc limit 1', 'order by ta.is_shipping_address desc, ta.address_type desc limit 1',
(doctype, name) (doctype, name)

View File

@ -10,36 +10,6 @@ frappe.query_reports["Accounts Payable"] = {
"options": "Company", "options": "Company",
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book"
},
{
"fieldname":"supplier",
"label": __("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
on_change: () => {
var supplier = frappe.query_report.get_filter_value('supplier');
frappe.db.get_value('Supplier', supplier, "tax_id", function(value) {
frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
});
}
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
},
{
"fieldname":"report_date",
"label": __("As on Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
},
{ {
"fieldname":"ageing_based_on", "fieldname":"ageing_based_on",
"label": __("Ageing Based On"), "label": __("Ageing Based On"),
@ -48,7 +18,10 @@ frappe.query_reports["Accounts Payable"] = {
"default": "Posting Date" "default": "Posting Date"
}, },
{ {
"fieldtype": "Break", "fieldname":"report_date",
"label": __("As on Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
}, },
{ {
"fieldname":"range1", "fieldname":"range1",
@ -71,6 +44,34 @@ frappe.query_reports["Accounts Payable"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book"
},
{
"fieldname":"supplier",
"label": __("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
on_change: () => {
var supplier = frappe.query_report.get_filter_value('supplier');
if (supplier) {
frappe.db.get_value('Supplier', supplier, "tax_id", function(value) {
frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
});
} else {
frappe.query_report.set_filter_value('tax_id', "");
}
}
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
},
{ {
"fieldname":"tax_id", "fieldname":"tax_id",
"label": __("Tax Id"), "label": __("Tax Id"),

View File

@ -10,24 +10,6 @@ frappe.query_reports["Accounts Payable Summary"] = {
"options": "Company", "options": "Company",
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{
"fieldname":"supplier",
"label": __("Supplier"),
"fieldtype": "Link",
"options": "Supplier"
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
},
{
"fieldname":"report_date",
"label": __("Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
},
{ {
"fieldname":"ageing_based_on", "fieldname":"ageing_based_on",
"label": __("Ageing Based On"), "label": __("Ageing Based On"),
@ -36,7 +18,10 @@ frappe.query_reports["Accounts Payable Summary"] = {
"default": "Posting Date" "default": "Posting Date"
}, },
{ {
"fieldtype": "Break", "fieldname":"report_date",
"label": __("Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
}, },
{ {
"fieldname":"range1", "fieldname":"range1",
@ -58,6 +43,24 @@ frappe.query_reports["Accounts Payable Summary"] = {
"fieldtype": "Int", "fieldtype": "Int",
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
},
{
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book"
},
{
"fieldname":"supplier",
"label": __("Supplier"),
"fieldtype": "Link",
"options": "Supplier"
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
} }
], ],

View File

@ -1,24 +1,14 @@
{% if(filters.show_pdc_in_print) { %}
<style>
@media screen {
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
font-family: Tahoma, sans-serif;
}
}
</style>
{% } %}
<style> <style>
.print-format { .print-format {
padding: 4mm; padding: 4mm;
font-size: 8.0pt !important; font-size: 8.0pt !important;
font-family: Tahoma, sans-serif; }
} .print-format td {
vertical-align:middle !important;
}
</style> </style>
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h4 class="text-center"> <h4 class="text-center">
{% if (filters.customer_name) { %} {% if (filters.customer_name) { %}
{%= filters.customer_name %} {%= filters.customer_name %}
@ -36,7 +26,20 @@
{%= __("Until") %} {%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %} {%= frappe.datetime.str_to_user(filters.report_date) %}
</h5> </h5>
<hr>
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
{% } %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) { %}
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% } %}
</div>
</div>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_pdc_in_print) { %}
{% var balance_row = data.slice(-1).pop(); {% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label; var range1 = report.columns[11].label;
@ -104,17 +107,21 @@
<thead> <thead>
<tr> <tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 18%">{%= __("Date") %}</th> <th style="width: 7%">{%= __("Date") %}</th>
<th style="width: 17%">{%= __("Reference") %}</th> <th style="width: 7%">{%= __("Age (Days)") %}</th>
<th style="width: 13%">{%= __("Reference") %}</th>
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th> <th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %} {% } %}
<th style="width: 10%">{%= __("Invoiced Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<th style="width: 10%">{%= __("Paid Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th> <th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %} {% } %}
<th style="width: 15%">{%= __("Outstanding Amount") %}</th> <th style="width: 15%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Customer LPO No.") %}</th> <th style="width: 10%">{%= __("Customer LPO No.") %}</th>
@ -139,6 +146,7 @@
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %} {% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td> <td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i][__("Age (Days)")] %}</td>
<td> <td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
{%= data[i]["voucher_type"] %} {%= data[i]["voucher_type"] %}
@ -146,6 +154,11 @@
{% } %} {% } %}
{%= data[i]["voucher_no"] %} {%= data[i]["voucher_no"] %}
</td> </td>
{% if(report.report_name === "Accounts Receivable") { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<td> <td>
{% if(!(filters.customer || filters.supplier)) { %} {% if(!(filters.customer || filters.supplier)) { %}
@ -156,10 +169,15 @@
<br> {%= data[i][__("Supplier Name")] %} <br> {%= data[i][__("Supplier Name")] %}
{% } %} {% } %}
{% } %} {% } %}
<br>{%= __("Remarks") %}: <div>
{%= data[i][__("Remarks")] %} {% if data[i][__("Remarks")] %}
{%= __("Remarks") %}:
{%= data[i][__("Remarks")] %}
{% } %}
</div>
</td> </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td>
@ -187,14 +205,18 @@
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<td></td> <td></td>
{% } %} {% } %}
<td><b>{%= __("Total") %}</b></td> {% if(report.report_name === "Accounts Receivable") { %}
<td></td>
{% } %}
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td> {%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["Debit Note"], data[i]["currency"]) %} </td> <td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>

View File

@ -10,6 +10,40 @@ frappe.query_reports["Accounts Receivable"] = {
"options": "Company", "options": "Company",
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{
"fieldname":"ageing_based_on",
"label": __("Ageing Based On"),
"fieldtype": "Select",
"options": 'Posting Date\nDue Date',
"default": "Posting Date"
},
{
"fieldname":"report_date",
"label": __("As on Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
},
{
"fieldname":"range1",
"label": __("Ageing Range 1"),
"fieldtype": "Int",
"default": "30",
"reqd": 1
},
{
"fieldname":"range2",
"label": __("Ageing Range 2"),
"fieldtype": "Int",
"default": "60",
"reqd": 1
},
{
"fieldname":"range3",
"label": __("Ageing Range 3"),
"fieldtype": "Int",
"default": "90",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),
@ -23,10 +57,19 @@ frappe.query_reports["Accounts Receivable"] = {
"options": "Customer", "options": "Customer",
on_change: () => { on_change: () => {
var customer = frappe.query_report.get_filter_value('customer'); var customer = frappe.query_report.get_filter_value('customer');
frappe.db.get_value('Customer', customer, ["tax_id", "customer_name"], function(value) { if (customer) {
frappe.query_report.set_filter_value('tax_id', value["tax_id"]); frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) {
frappe.query_report.set_filter_value('customer_name', value["customer_name"]); frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
}); frappe.query_report.set_filter_value('customer_name', value["customer_name"]);
frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]);
frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]);
});
} else {
frappe.query_report.set_filter_value('tax_id', "");
frappe.query_report.set_filter_value('customer_name', "");
frappe.query_report.set_filter_value('credit_limit', "");
frappe.query_report.set_filter_value('payment_terms', "");
}
} }
}, },
{ {
@ -59,43 +102,6 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Sales Person" "options": "Sales Person"
}, },
{
"fieldtype": "Break",
},
{
"fieldname":"report_date",
"label": __("As on Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
},
{
"fieldname":"ageing_based_on",
"label": __("Ageing Based On"),
"fieldtype": "Select",
"options": 'Posting Date\nDue Date',
"default": "Posting Date"
},
{
"fieldname":"range1",
"label": __("Ageing Range 1"),
"fieldtype": "Int",
"default": "30",
"reqd": 1
},
{
"fieldname":"range2",
"label": __("Ageing Range 2"),
"fieldtype": "Int",
"default": "60",
"reqd": 1
},
{
"fieldname":"range3",
"label": __("Ageing Range 3"),
"fieldtype": "Int",
"default": "90",
"reqd": 1
},
{ {
"fieldname":"show_pdc_in_print", "fieldname":"show_pdc_in_print",
"label": __("Show PDC in Print"), "label": __("Show PDC in Print"),
@ -112,6 +118,18 @@ frappe.query_reports["Accounts Receivable"] = {
"label": __("Customer Name"), "label": __("Customer Name"),
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1 "hidden": 1
},
{
"fieldname":"payment_terms",
"label": __("Payment Tems"),
"fieldtype": "Data",
"hidden": 1
},
{
"fieldname":"credit_limit",
"label": __("Credit Limit"),
"fieldtype": "Currency",
"hidden": 1
} }
], ],

View File

@ -125,17 +125,22 @@ class ReceivablePayableReport(object):
}] }]
if args.get('party_type') == 'Customer': if args.get('party_type') == 'Customer':
columns.append({
"label": _("Customer LPO"),
"fieldtype": "Data",
"fieldname": "po_no",
"width": 100,
})
columns += [_("Delivery Note") + ":Data:100"]
if args.get("party_type") == "Customer":
columns += [ columns += [
{
"label": _("Customer LPO"),
"fieldtype": "Data",
"fieldname": "po_no",
"width": 100,
},
_("Delivery Note") + ":Data:100",
_("Territory") + ":Link/Territory:80", _("Territory") + ":Link/Territory:80",
_("Customer Group") + ":Link/Customer Group:120" _("Customer Group") + ":Link/Customer Group:120",
{
"label": _("Sales Person"),
"fieldtype": "Data",
"fieldname": "sales_person",
"width": 120,
}
] ]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
columns += [_("Supplier Group") + ":Link/Supplier Group:80"] columns += [_("Supplier Group") + ":Link/Supplier Group:80"]
@ -238,7 +243,8 @@ class ReceivablePayableReport(object):
# customer territory / supplier group # customer territory / supplier group
if args.get("party_type") == "Customer": if args.get("party_type") == "Customer":
row += [self.get_territory(gle.party), self.get_customer_group(gle.party)] row += [self.get_territory(gle.party), self.get_customer_group(gle.party),
voucher_details.get(gle.voucher_no, {}).get("sales_person")]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(gle.party)] row += [self.get_supplier_group(gle.party)]
@ -395,9 +401,14 @@ class ReceivablePayableReport(object):
values.append(self.filters.get("sales_partner")) values.append(self.filters.get("sales_partner"))
if self.filters.get("sales_person"): if self.filters.get("sales_person"):
conditions.append("""party in (select parent lft, rgt = frappe.db.get_value("Sales Person",
from `tabSales Team` where sales_person=%s and parenttype = 'Customer')""") self.filters.get("sales_person"), ["lft", "rgt"])
values.append(self.filters.get("sales_person"))
conditions.append("""exists(select name from `tabSales Team` steam where
steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
if party_type_field=="supplier": if party_type_field=="supplier":
if self.filters.get("supplier_group"): if self.filters.get("supplier_group"):
@ -550,8 +561,12 @@ def get_voucher_details(party_type, voucher_nos, dn_details):
voucher_details = frappe._dict() voucher_details = frappe._dict()
if party_type == "Customer": if party_type == "Customer":
for si in frappe.db.sql("""select name, due_date, po_no for si in frappe.db.sql("""
from `tabSales Invoice` where docstatus=1 and name in (%s) select inv.name, inv.due_date, inv.po_no, GROUP_CONCAT(steam.sales_person SEPARATOR ', ') as sales_person
from `tabSales Invoice` inv
left join `tabSales Team` steam on steam.parent = inv.name and steam.parenttype = 'Sales Invoice'
where inv.docstatus=1 and inv.name in (%s)
group by inv.name
""" %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1): """ %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1):
si['delivery_note'] = dn_details.get(si.name) si['delivery_note'] = dn_details.get(si.name)
voucher_details.setdefault(si.name, si) voucher_details.setdefault(si.name, si)

View File

@ -11,25 +11,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{ {
"fieldname":"customer", "fieldname":"ageing_based_on",
"label": __("Customer"), "label": __("Ageing Based On"),
"fieldtype": "Link", "fieldtype": "Select",
"options": "Customer" "options": 'Posting Date\nDue Date',
}, "default": "Posting Date"
{
"fieldname":"customer_group",
"label": __("Customer Group"),
"fieldtype": "Link",
"options": "Customer Group"
},
{
"fieldname":"payment_terms_template",
"label": __("Payment Terms Template"),
"fieldtype": "Link",
"options": "Payment Terms Template"
},
{
"fieldtype": "Break",
}, },
{ {
"fieldname":"report_date", "fieldname":"report_date",
@ -37,13 +23,6 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.datetime.get_today() "default": frappe.datetime.get_today()
}, },
{
"fieldname":"ageing_based_on",
"label": __("Ageing Based On"),
"fieldtype": "Select",
"options": 'Posting Date\nDue Date',
"default": "Posting Date"
},
{ {
"fieldname":"range1", "fieldname":"range1",
"label": __("Ageing Range 1"), "label": __("Ageing Range 1"),
@ -64,6 +43,48 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"fieldtype": "Int", "fieldtype": "Int",
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
},
{
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book"
},
{
"fieldname":"customer",
"label": __("Customer"),
"fieldtype": "Link",
"options": "Customer"
},
{
"fieldname":"customer_group",
"label": __("Customer Group"),
"fieldtype": "Link",
"options": "Customer Group"
},
{
"fieldname":"payment_terms_template",
"label": __("Payment Terms Template"),
"fieldtype": "Link",
"options": "Payment Terms Template"
},
{
"fieldname":"territory",
"label": __("Territory"),
"fieldtype": "Link",
"options": "Territory"
},
{
"fieldname":"sales_partner",
"label": __("Sales Partner"),
"fieldtype": "Link",
"options": "Sales Partner"
},
{
"fieldname":"sales_person",
"label": __("Sales Person"),
"fieldtype": "Link",
"options": "Sales Person"
} }
], ],

View File

@ -22,7 +22,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
columns += [ args.get("party_type") + " Name::140"] columns += [ args.get("party_type") + " Name::140"]
credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt" credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt"
columns += [{ columns += [{
"label": _("Total Invoiced Amt"), "label": _("Total Invoiced Amt"),
"fieldname": "total_invoiced_amt", "fieldname": "total_invoiced_amt",
@ -97,6 +97,12 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"fieldtype": "Link", "fieldtype": "Link",
"options": "Customer Group", "options": "Customer Group",
"width": 80 "width": 80
},
{
"label": _("Sales Person"),
"fieldtype": "Data",
"fieldname": "sales_person",
"width": 120,
}] }]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
@ -107,7 +113,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"options": "Supplier Group", "options": "Supplier Group",
"width": 80 "width": 80
}] }]
columns.append({ columns.append({
"fieldname": "currency", "fieldname": "currency",
"label": _("Currency"), "label": _("Currency"),
@ -135,10 +141,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
] ]
if args.get("party_type") == "Customer": if args.get("party_type") == "Customer":
row += [self.get_territory(party), self.get_customer_group(party)] row += [self.get_territory(party), self.get_customer_group(party), ", ".join(set(party_dict.sales_person))]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(party)] row += [self.get_supplier_group(party)]
row.append(party_dict.currency) row.append(party_dict.currency)
data.append(row) data.append(row)
@ -156,15 +162,19 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range1": 0, "range1": 0,
"range2": 0, "range2": 0,
"range3": 0, "range3": 0,
"range4": 0 "range4": 0,
"sales_person": []
}) })
) )
for k in list(party_total[d.party]): for k in list(party_total[d.party]):
if k != "currency": if k not in ["currency", "sales_person"]:
party_total[d.party][k] += flt(d.get(k, 0)) party_total[d.party][k] += flt(d.get(k, 0))
party_total[d.party].currency = d.currency party_total[d.party].currency = d.currency
if d.sales_person:
party_total[d.party].sales_person.append(d.sales_person)
return party_total return party_total
def get_voucherwise_data(self, party_naming_by, args): def get_voucherwise_data(self, party_naming_by, args):
@ -181,12 +191,13 @@ class AccountsReceivableSummary(ReceivablePayableReport):
cols += ["bill_no", "bill_date"] cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt", cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "currency"] "outstanding_amt", "age", "range1", "range2", "range3", "range4", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount", "remaining_balance"]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
cols += ["supplier_group", "remarks"] cols += ["supplier_group", "remarks"]
if args.get("party_type") == "Customer": if args.get("party_type") == "Customer":
cols += ["territory", "customer_group", "remarks"] cols += ["po_no", "do_no", "territory", "customer_group", "sales_person", "remarks"]
return self.make_data_dict(cols, voucherwise_data) return self.make_data_dict(cols, voucherwise_data)

View File

@ -16,8 +16,6 @@ def execute(filters=None):
return [], [] return [], []
account_details = {} account_details = {}
if not filters.get("group_by"):
filters['group_by'] = _('Group by Voucher (Consolidated)')
if filters and filters.get('print_in_account_currency') and \ if filters and filters.get('print_in_account_currency') and \
not filters.get('account'): not filters.get('account'):

View File

@ -236,7 +236,7 @@ class GrossProfitGenerator(object):
previous_stock_value = len(my_sle) > i+1 and \ previous_stock_value = len(my_sle) > i+1 and \
flt(my_sle[i+1].stock_value) or 0.0 flt(my_sle[i+1].stock_value) or 0.0
if previous_stock_value: if previous_stock_value:
return previous_stock_value - flt(sle.stock_value) return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else: else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code) return flt(row.qty) * self.get_average_buying_rate(row, item_code)
else: else:

View File

@ -231,6 +231,13 @@ def get_columns():
"options": "Account", "options": "Account",
"width": 300 "width": 300
}, },
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
},
{ {
"fieldname": "opening_debit", "fieldname": "opening_debit",
"label": _("Opening (Dr)"), "label": _("Opening (Dr)"),
@ -272,13 +279,6 @@ def get_columns():
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 120 "width": 120
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
} }
] ]

View File

@ -0,0 +1,30 @@
{
"add_total_row": 1,
"creation": "2018-11-22 16:53:19.167935",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-11-22 17:40:11.317567",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Trial Balance (Simple)",
"owner": "Administrator",
"prepared_report": 0,
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
"ref_doctype": "GL Entry",
"report_name": "Trial Balance (Simple)",
"report_type": "Query Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
}

View File

@ -315,7 +315,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request", source_doctype: "Material Request",
target: me.frm, target: me.frm,
args: args,
setters: { setters: {
company: me.frm.doc.company company: me.frm.doc.company
}, },

View File

@ -1 +0,0 @@
Trends of purchases across Items, Item Groups, Suppliers.

View File

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

View File

@ -1,231 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.pages['purchase-analytics'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __('Purchase Analytics'),
single_column: true
});
new erpnext.PurchaseAnalytics(wrapper);
frappe.breadcrumbs.add("Buying");
}
erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({
init: function(wrapper) {
this._super({
title: __("Purchase Analytics"),
parent: $(wrapper).find('.layout-main'),
page: wrapper.page,
doctypes: ["Item", "Item Group", "Supplier", "Supplier Group", "Company", "Fiscal Year",
"Purchase Invoice", "Purchase Invoice Item",
"Purchase Order", "Purchase Order Item[Purchase Analytics]",
"Purchase Receipt", "Purchase Receipt Item[Purchase Analytics]"],
tree_grid: { show: true }
});
this.tree_grids = {
"Supplier Group": {
label: __("Supplier Group / Supplier"),
show: true,
item_key: "supplier",
parent_field: "parent_supplier_group",
formatter: function(item) {
return item.supplier_name ? item.supplier_name + " (" + item.name + ")" : item.name;
}
},
"Supplier": {
label: __("Supplier"),
show: false,
item_key: "supplier",
formatter: function(item) {
return item.supplier_name ? item.supplier_name + " (" + item.name + ")" : item.name;
}
},
"Item Group": {
label: "Item",
show: true,
parent_field: "parent_item_group",
item_key: "item_code",
formatter: function(item) {
return item.name;
}
},
"Item": {
label: "Item",
show: false,
item_key: "item_code",
formatter: function(item) {
return item.name;
}
},
}
},
setup_columns: function() {
this.tree_grid = this.tree_grids[this.tree_type];
var std_columns = [
{id: "name", name: this.tree_grid.label, field: "name", width: 300},
{id: "total", name: "Total", field: "total", plot: false,
formatter: this.currency_formatter}
];
this.make_date_range_columns();
this.columns = std_columns.concat(this.columns);
},
filters: [
{fieldtype:"Select", label: __("Tree Type"), fieldname: "tree_type",
options:["Supplier Group", "Supplier", "Item Group", "Item"],
filter: function(val, item, opts, me) {
return me.apply_zero_filter(val, item, opts, me);
}},
{fieldtype:"Select", label: __("Based On"), fieldname: "based_on",
options:["Purchase Invoice", "Purchase Order", "Purchase Receipt"]},
{fieldtype:"Select", label: __("Value or Qty"), fieldname: "value_or_qty",
options:["Value", "Quantity"]},
{fieldtype:"Select", label: __("Company"), link:"Company", fieldname: "company",
default_value: __("Select Company...")},
{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
{fieldtype:"Select", label: __("Range"), fieldname: "range",
options:[{label: __("Daily"), value: "Daily"}, {label: __("Weekly"), value: "Weekly"},
{label: __("Monthly"), value: "Monthly"}, {label: __("Quarterly"), value: "Quarterly"},
{label: __("Yearly"), value: "Yearly"}]}
],
setup_filters: function() {
var me = this;
this._super();
this.trigger_refresh_on_change(["value_or_qty", "tree_type", "based_on", "company"]);
this.show_zero_check();
},
init_filter_values: function() {
this._super();
this.filter_inputs.range.val('Monthly');
},
prepare_data: function() {
var me = this;
if (!this.tl) {
// add 'Not Set' Supplier & Item
// (Supplier / Item are not mandatory!!)
frappe.report_dump.data["Supplier"].push({
name: __("Not Set"),
parent_supplier_group: __("All Supplier Groups"),
id: "Not Set",
});
frappe.report_dump.data["Item"].push({
name: __("Not Set"),
parent_item_group: "All Item Groups",
id: "Not Set",
});
}
if (!this.tl || !this.tl[this.based_on]) {
this.make_transaction_list(this.based_on, this.based_on + " Item");
}
if(!this.data || me.item_type != me.tree_type) {
var items;
if(me.tree_type=='Supplier') {
items = frappe.report_dump.data["Supplier"];
} else if(me.tree_type=='Supplier Group') {
items = this.prepare_tree("Supplier", "Supplier Group");
} else if(me.tree_type=="Item Group") {
items = this.prepare_tree("Item", "Item Group");
} else if(me.tree_type=="Item") {
items = frappe.report_dump.data["Item"];
}
me.item_type = me.tree_type
me.parent_map = {};
me.item_by_name = {};
me.data = [];
$.each(items, function(i, v) {
var d = copy_dict(v);
me.data.push(d);
me.item_by_name[d.name] = d;
if(d[me.tree_grid.parent_field]) {
me.parent_map[d.name] = d[me.tree_grid.parent_field];
}
me.reset_item_values(d);
});
this.set_indent();
} else {
// otherwise, only reset values
$.each(this.data, function(i, d) {
me.reset_item_values(d);
});
}
this.prepare_balances();
if(me.tree_grid.show) {
this.set_totals(false);
this.update_groups();
} else {
this.set_totals(true);
}
},
prepare_balances: function() {
var me = this;
var from_date = frappe.datetime.str_to_obj(this.from_date);
var to_date = frappe.datetime.str_to_obj(this.to_date);
var is_val = this.value_or_qty == 'Value';
$.each(this.tl[this.based_on], function(i, tl) {
if (me.is_default('company') ? true : tl.company === me.company) {
var posting_date = frappe.datetime.str_to_obj(tl.posting_date);
if (posting_date >= from_date && posting_date <= to_date) {
var item = me.item_by_name[tl[me.tree_grid.item_key]] ||
me.item_by_name['Not Set'];
item[me.column_map[tl.posting_date].field] += (is_val ? tl.base_net_amount : tl.qty);
}
}
});
},
update_groups: function() {
var me = this;
$.each(this.data, function(i, item) {
var parent = me.parent_map[item.name];
while(parent) {
var parent_group = me.item_by_name[parent];
$.each(me.columns, function(c, col) {
if (col.formatter == me.currency_formatter) {
parent_group[col.field] =
flt(parent_group[col.field])
+ flt(item[col.field]);
}
});
parent = me.parent_map[parent];
}
});
},
set_totals: function(sort) {
var me = this;
var checked = false;
$.each(this.data, function(i, d) {
d.total = 0.0;
$.each(me.columns, function(i, col) {
if(col.formatter==me.currency_formatter && !col.hidden && col.field!="total")
d.total += d[col.field];
if(d.checked) checked = true;
})
});
if(sort)this.data = this.data.sort(function(a, b) { return b.total - a.total; });
if(!this.checked) {
this.data[0].checked = true;
}
}
});

View File

@ -1,23 +0,0 @@
{
"creation": "2012-09-21 20:15:16.000000",
"docstatus": 0,
"doctype": "Page",
"icon": "fa fa-bar-chart",
"idx": 1,
"modified": "2013-07-11 14:43:52.000000",
"modified_by": "Administrator",
"module": "Buying",
"name": "purchase-analytics",
"owner": "Administrator",
"page_name": "purchase-analytics",
"roles": [
{
"role": "Analytics"
},
{
"role": "Purchase Manager"
}
],
"standard": "Yes",
"title": "Purchase Analytics"
}

View File

@ -68,11 +68,8 @@ frappe.query_reports["Purchase Analytics"] = {
} }
], ],
"formatter": function(value, row, column, data) { after_datatable_render: function(datatable_obj) {
if(!value){ $(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click();
value = 0
}
return value;
}, },
get_datatable_options(options) { get_datatable_options(options) {
return Object.assign(options, { return Object.assign(options, {
@ -110,19 +107,19 @@ frappe.query_reports["Purchase Analytics"] = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets
} }
setTimeout(() => { setTimeout(() => {
frappe.query_report.chart.update(new_data) frappe.query_report.chart.update(new_data)
},200) },500)
setTimeout(() => { setTimeout(() => {
frappe.query_report.chart.draw(true); frappe.query_report.chart.draw(true);
}, 800) }, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },
} }
}) });
}, }
} }

View File

@ -1,8 +1,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe import _ from frappe import _
import frappe
def get_data(): def get_data():
return [ config = [
{ {
"label": _("Billing"), "label": _("Billing"),
"items": [ "items": [
@ -25,48 +27,7 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Payment Entry", "name": "Payment Entry",
"description": _("Bank/Cash transactions against party or for internal transfer") "description": _("Bank/Cash transactions against party or for internal transfer")
}, }
{
"type": "page",
"name": "pos",
"label": _("POS"),
"description": _("Point of Sale")
},
{
"type": "doctype",
"name": "Cashier Closing",
"description": _("Cashier Closing")
},
{
"type": "doctype",
"name": "Auto Repeat",
"label": _("Auto Repeat"),
"description": _("To make recurring documents")
},
{
"type": "doctype",
"name": "Loyalty Program",
"label": _("Loyalty Program"),
"description": _("To make Customer based incentive schemes.")
},
{
"type": "doctype",
"name": "Loyalty Point Entry",
"label": _("Loyalty Point Entry"),
"description": _("To view logs of Loyalty Points assigned to a Customer.")
},
{
"type": "report",
"name": "Accounts Receivable",
"doctype": "Sales Invoice",
"is_query_report": True
},
{
"type": "report",
"name": "Accounts Payable",
"doctype": "Purchase Invoice",
"is_query_report": True
},
] ]
}, },
@ -93,7 +54,7 @@ def get_data():
}, },
{ {
"type": "report", "type": "report",
"name":"General Ledger", "name": "General Ledger",
"doctype": "GL Entry", "doctype": "GL Entry",
"is_query_report": True, "is_query_report": True,
}, },
@ -121,6 +82,18 @@ def get_data():
{ {
"label": _("Accounting Statements"), "label": _("Accounting Statements"),
"items": [ "items": [
{
"type": "report",
"name": "Accounts Receivable",
"doctype": "Sales Invoice",
"is_query_report": True
},
{
"type": "report",
"name": "Accounts Payable",
"doctype": "Purchase Invoice",
"is_query_report": True
},
{ {
"type": "report", "type": "report",
"name": "Trial Balance", "name": "Trial Balance",
@ -224,49 +197,6 @@ def get_data():
}, },
] ]
}, },
{
"label": _("Goods and Services Tax (GST India)"),
"items": [
{
"type": "doctype",
"name": "GST Settings",
},
{
"type": "doctype",
"name": "GST HSN Code",
},
{
"type": "report",
"name": "GSTR-1",
"is_query_report": True
},
{
"type": "report",
"name": "GSTR-2",
"is_query_report": True
},
{
"type": "report",
"name": "GST Sales Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Purchase Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Itemised Sales Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Itemised Purchase Register",
"is_query_report": True
},
]
},
{ {
"label": _("Budget and Cost Center"), "label": _("Budget and Cost Center"),
"items": [ "items": [
@ -290,7 +220,7 @@ def get_data():
"doctype": "Cost Center" "doctype": "Cost Center"
}, },
{ {
"type":"doctype", "type": "doctype",
"name": "Monthly Distribution", "name": "Monthly Distribution",
"description": _("Seasonality for setting budgets, targets etc.") "description": _("Seasonality for setting budgets, targets etc.")
}, },
@ -347,29 +277,24 @@ def get_data():
}, },
{ {
"type": "doctype", "type": "doctype",
"name": "POS Settings", "name": "Terms and Conditions",
"description": _("Setup mode of POS (Online / Offline)")
},
{
"type": "doctype",
"name": "POS Profile",
"label": _("Point-of-Sale Profile"),
"description": _("Setup default values for POS Invoices")
},
{
"type": "doctype",
"name":"Terms and Conditions",
"label": _("Terms and Conditions Template"), "label": _("Terms and Conditions Template"),
"description": _("Template of terms or contract.") "description": _("Template of terms or contract.")
}, },
{ {
"type": "doctype", "type": "doctype",
"name":"Mode of Payment", "name": "Mode of Payment",
"description": _("e.g. Bank, Cash, Credit Card") "description": _("e.g. Bank, Cash, Credit Card")
}, },
{ {
"type": "doctype", "type": "doctype",
"name":"C-Form", "name": "Auto Repeat",
"label": _("Auto Repeat"),
"description": _("To make recurring documents")
},
{
"type": "doctype",
"name": "C-Form",
"description": _("C-Form records"), "description": _("C-Form records"),
"country": "India" "country": "India"
} }
@ -506,12 +431,12 @@ def get_data():
"items": [ "items": [
{ {
"type": "doctype", "type": "doctype",
"name":"Shareholder", "name": "Shareholder",
"description": _("List of available Shareholders with folio numbers") "description": _("List of available Shareholders with folio numbers")
}, },
{ {
"type": "doctype", "type": "doctype",
"name":"Share Transfer", "name": "Share Transfer",
"description": _("List of all share transactions"), "description": _("List of all share transactions"),
}, },
{ {
@ -528,28 +453,6 @@ def get_data():
} }
] ]
}, },
{
"label": _("Subscription Management"),
"icon": "fa fa-microchip ",
"items": [
{
"type": "doctype",
"name":"Subscriber",
},
{
"type": "doctype",
"name":"Subscription Plan",
},
{
"type": "doctype",
"name":"Subscription"
},
{
"type": "doctype",
"name": "Subscription Settings"
}
]
},
{ {
"label": _("Help"), "label": _("Help"),
"icon": "fa fa-facetime-video", "icon": "fa fa-facetime-video",
@ -572,3 +475,121 @@ def get_data():
] ]
} }
] ]
gst = {
"label": _("Goods and Services Tax (GST India)"),
"items": [
{
"type": "doctype",
"name": "GST Settings",
},
{
"type": "doctype",
"name": "GST HSN Code",
},
{
"type": "report",
"name": "GSTR-1",
"is_query_report": True
},
{
"type": "report",
"name": "GSTR-2",
"is_query_report": True
},
{
"type": "report",
"name": "GST Sales Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Purchase Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Itemised Sales Register",
"is_query_report": True
},
{
"type": "report",
"name": "GST Itemised Purchase Register",
"is_query_report": True
},
]
}
retail = {
"label": _("Retail Operations"),
"items": [
{
"type": "page",
"name": "pos",
"label": _("POS"),
"description": _("Point of Sale")
},
{
"type": "doctype",
"name": "Cashier Closing",
"description": _("Cashier Closing")
},
{
"type": "doctype",
"name": "POS Settings",
"description": _("Setup mode of POS (Online / Offline)")
},
{
"type": "doctype",
"name": "POS Profile",
"label": _("Point-of-Sale Profile"),
"description": _("Setup default values for POS Invoices")
},
{
"type": "doctype",
"name": "Loyalty Program",
"label": _("Loyalty Program"),
"description": _("To make Customer based incentive schemes.")
},
{
"type": "doctype",
"name": "Loyalty Point Entry",
"label": _("Loyalty Point Entry"),
"description": _("To view logs of Loyalty Points assigned to a Customer.")
}
]
}
subscriptions = {
"label": _("Subscription Management"),
"icon": "fa fa-microchip ",
"items": [
{
"type": "doctype",
"name": "Subscriber",
},
{
"type": "doctype",
"name": "Subscription Plan",
},
{
"type": "doctype",
"name": "Subscription"
},
{
"type": "doctype",
"name": "Subscription Settings"
}
]
}
countries = frappe.get_all("Company", fields="country")
countries = [country["country"] for country in countries]
if "India" in countries:
config.insert(7, gst)
domains = frappe.get_active_domains()
if "Retail" in domains:
config.insert(2, retail)
else:
config.insert(7, retail)
if "Services" in domains:
config.insert(2, subscriptions)
else:
config.insert(7, subscriptions)
return config

View File

@ -57,6 +57,8 @@ class AccountsController(TransactionBase):
_('{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):
self.validate_qty_is_not_zero()
if self.get("_action") and self._action != "update_after_submit": if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True) self.set_missing_values(for_validate=True)
@ -179,7 +181,7 @@ class AccountsController(TransactionBase):
validate_due_date(self.posting_date, self.due_date, validate_due_date(self.posting_date, self.due_date,
"Customer", self.customer, self.company, self.payment_terms_template) "Customer", self.customer, self.company, self.payment_terms_template)
elif self.doctype == "Purchase Invoice": elif self.doctype == "Purchase Invoice":
validate_due_date(self.posting_date, self.due_date, validate_due_date(self.bill_date or self.posting_date, self.due_date,
"Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template) "Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template)
def set_price_list_currency(self, buying_or_selling): def set_price_list_currency(self, buying_or_selling):
@ -359,6 +361,11 @@ class AccountsController(TransactionBase):
return gl_dict return gl_dict
def validate_qty_is_not_zero(self):
for item in self.items:
if not item.qty:
frappe.throw("Item quantity can not be zero")
def validate_account_currency(self, account, account_currency=None): def validate_account_currency(self, account, account_currency=None):
valid_currency = [self.company_currency] valid_currency = [self.company_currency]
if self.get("currency") and self.currency != self.company_currency: if self.get("currency") and self.currency != self.company_currency:
@ -405,7 +412,8 @@ class AccountsController(TransactionBase):
if d.against_order: if d.against_order:
allocated_amount = flt(d.amount) allocated_amount = flt(d.amount)
else: else:
allocated_amount = min(self.grand_total - advance_allocated, d.amount) amount = self.rounded_total or self.grand_total
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount) advance_allocated += flt(allocated_amount)
self.append("advances", { self.append("advances", {

View File

@ -12,6 +12,7 @@ from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items
from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
@ -42,6 +43,7 @@ class BuyingController(StockController):
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.validate_warehouse() self.validate_warehouse()
self.set_supplier_address()
if self.doctype=="Purchase Invoice": if self.doctype=="Purchase Invoice":
self.validate_purchase_receipt_if_update_stock() self.validate_purchase_receipt_if_update_stock()
@ -113,6 +115,16 @@ class BuyingController(StockController):
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]: if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
d.db_set('cost_center', lc_voucher_data[0][1]) d.db_set('cost_center', lc_voucher_data[0][1])
def set_supplier_address(self):
address_dict = {
'supplier_address': 'address_display',
'shipping_address': 'shipping_address_display'
}
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
def set_total_in_words(self): def set_total_in_words(self):
from frappe.utils import money_in_words from frappe.utils import money_in_words
if self.meta.get_field("base_in_words"): if self.meta.get_field("base_in_words"):

View File

@ -9,6 +9,7 @@ from erpnext.stock.get_item_details import get_bin_details
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.doctype.item.item import get_item_defaults, set_item_default from erpnext.stock.doctype.item.item import get_item_defaults, set_item_default
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
@ -16,7 +17,7 @@ class SellingController(StockController):
def __setup__(self): def __setup__(self):
if hasattr(self, "taxes"): if hasattr(self, "taxes"):
self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings", self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings",
"print_taxes_with_zero_amount")) "print_taxes_with_zero_amount"))
self.flags.show_inclusive_tax_in_print = self.is_inclusive_tax() self.flags.show_inclusive_tax_in_print = self.is_inclusive_tax()
self.print_templates = { self.print_templates = {
@ -41,7 +42,9 @@ class SellingController(StockController):
self.validate_selling_price() self.validate_selling_price()
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.set_po_nos() self.set_po_nos()
self.set_gross_profit()
set_default_income_account_for_item(self) set_default_income_account_for_item(self)
self.set_customer_address()
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
@ -348,6 +351,23 @@ class SellingController(StockController):
if po_nos and po_nos[0].get('po_no'): if po_nos and po_nos[0].get('po_no'):
self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no]))) self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no])))
def set_gross_profit(self):
if self.doctype == "Sales Order":
for item in self.items:
item.gross_profit = flt(((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item))
def set_customer_address(self):
address_dict = {
'customer_address': 'address_display',
'shipping_address_name': 'shipping_address',
'company_address': 'company_address_display'
}
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
def validate_items(self): def validate_items(self):
# validate items to see if they have is_sales_item enabled # validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type from erpnext.controllers.buying_controller import validate_item_type

View File

@ -65,6 +65,7 @@ class calculate_taxes_and_totals(object):
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']: if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0: if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
item.discount_amount = item.rate_with_margin - item.rate item.discount_amount = item.rate_with_margin - item.rate
@ -647,4 +648,4 @@ def get_rounded_tax_amount(itemised_tax, precision):
# Rounding based on tax_amount precision # Rounding based on tax_amount precision
for taxes in itemised_tax.values(): for taxes in itemised_tax.values():
for tax_account in taxes: for tax_account in taxes:
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)

View File

@ -376,7 +376,7 @@ def setup_pos_profile():
company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr") company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr")
pos = frappe.new_doc('POS Profile') pos = frappe.new_doc('POS Profile')
pos.user = frappe.db.get_global('demo_accounts_user') pos.user = frappe.db.get_global('demo_accounts_user')
pos.pos_profile_name = "Demo POS Profile" pos.name = "Demo POS Profile"
pos.naming_series = 'SINV-' pos.naming_series = 'SINV-'
pos.update_stock = 0 pos.update_stock = 0
pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr

View File

@ -38,6 +38,12 @@ frappe.ui.form.on("Assessment Result", {
frappe.ui.form.on("Assessment Result Detail", { frappe.ui.form.on("Assessment Result Detail", {
score: function(frm, cdt, cdn) { score: function(frm, cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if(!d.maximum_score || !frm.doc.grading_scale) {
d.score = "";
frappe.throw(__("Please fill in all the details to generate Assessment Result."));
}
if (d.score > d.maximum_score) { if (d.score > d.maximum_score) {
frappe.throw(__("Score cannot be greater than Maximum Score")); frappe.throw(__("Score cannot be greater than Maximum Score"));
} }

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
@ -991,39 +992,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "paid_amount",
"fieldtype": "Currency",
"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": "Paid Amount",
"length": 0,
"no_copy": 1,
"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_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -1360,7 +1328,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2018-08-21 14:44:48.968839", "modified": "2018-11-26 20:42:14.467284",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Fees", "name": "Fees",

View File

@ -112,7 +112,10 @@ def get_fee_list(doctype, txt, filters, limit_start, limit_page_length=20, order
user = frappe.session.user user = frappe.session.user
student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user) student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user)
if student: if student:
return frappe. db.sql('''select name, program, due_date, paid_amount, outstanding_amount, grand_total from `tabFees` return frappe. db.sql('''
select name, program, due_date, grand_total - outstanding_amount as paid_amount,
outstanding_amount, grand_total, currency
from `tabFees`
where student= %s and docstatus=1 where student= %s and docstatus=1
order by due_date asc limit {0} , {1}''' order by due_date asc limit {0} , {1}'''
.format(limit_start, limit_page_length), student, as_dict = True) .format(limit_start, limit_page_length), student, as_dict = True)

View File

@ -1,18 +1,18 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2016-06-22 02:58:41.024538", "creation": "2016-06-22 02:58:41.024538",
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-10 19:41:37.320224", "modified": "2018-12-17 16:46:46.176620",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student Fee Collection", "name": "Student Fee Collection",
"owner": "Administrator", "owner": "Administrator",
"query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(paid_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student", "prepared_report": 0,
"query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student",
"ref_doctype": "Fees", "ref_doctype": "Fees",
"report_name": "Student Fee Collection", "report_name": "Student Fee Collection",
"report_type": "Query Report", "report_type": "Query Report",

View File

@ -120,3 +120,16 @@ var btn_invoice_registration = function (frm) {
} }
}); });
}; };
frappe.ui.form.on('Patient Relation', {
patient_relation_add: function(frm){
frm.fields_dict['patient_relation'].grid.get_field('patient').get_query = function(frm){
var patient_list = [];
if(!frm.doc.__islocal) patient_list.push(frm.doc.name);
$.each(frm.doc.patient_relation, function(idx, val){
if (val.patient) patient_list.push(val.patient);
});
return { filters: [['Patient', 'name', 'not in', patient_list]] };
};
}
});

View File

@ -39,7 +39,9 @@ frappe.ui.form.on('Patient Appointment', {
frm.add_custom_button(__('Cancel'), function() { frm.add_custom_button(__('Cancel'), function() {
btn_update_status(frm, "Cancelled"); btn_update_status(frm, "Cancelled");
}); });
frm.add_custom_button(__('Reschedule'), function() {
check_and_set_availability(frm);
});
if(frm.doc.procedure_template){ if(frm.doc.procedure_template){
frm.add_custom_button(__("Procedure"),function(){ frm.add_custom_button(__("Procedure"),function(){
btn_create_procedure(frm); btn_create_procedure(frm);
@ -59,7 +61,9 @@ frappe.ui.form.on('Patient Appointment', {
frm.add_custom_button(__('Cancel'), function() { frm.add_custom_button(__('Cancel'), function() {
btn_update_status(frm, "Cancelled"); btn_update_status(frm, "Cancelled");
}); });
frm.add_custom_button(__('Reschedule'), function() {
check_and_set_availability(frm);
});
if(frm.doc.procedure_template){ if(frm.doc.procedure_template){
frm.add_custom_button(__("Procedure"),function(){ frm.add_custom_button(__("Procedure"),function(){
btn_create_procedure(frm); btn_create_procedure(frm);
@ -100,117 +104,7 @@ frappe.ui.form.on('Patient Appointment', {
}); });
}, },
check_availability: function(frm) { check_availability: function(frm) {
var { practitioner, appointment_date } = frm.doc; check_and_set_availability(frm);
if(!(practitioner && appointment_date)) {
frappe.throw(__("Please select Healthcare Practitioner and Date"));
}
// show booking modal
frm.call({
method: 'get_availability_data',
args: {
practitioner: practitioner,
date: appointment_date
},
callback: (r) => {
var data = r.message;
if(data.slot_details.length > 0){
show_availability(data);
}else{
show_empty_state();
}
}
});
function show_empty_state() {
frappe.msgprint({
title: __('Not Available'),
message: __("Healthcare Practitioner {0} not available on {1}", [practitioner.bold(), appointment_date.bold()]),
indicator: 'red'
});
}
function show_availability(data) {
var d = new frappe.ui.Dialog({
title: __("Available slots"),
fields: [{ fieldtype: 'HTML', fieldname: 'available_slots'}],
primary_action_label: __("Book"),
primary_action: function() {
// book slot
var btn_selected = $wrapper.find('button.btn-selected-slot');
frm.set_value('appointment_time', btn_selected.attr('data-name'));
frm.set_value('service_unit', btn_selected.attr('data-service-unit') || '');
frm.set_value('duration', btn_selected.attr('data-duration'));
d.hide();
frm.enable_save();
frm.save();
frm.enable_save();
}
});
var $wrapper = d.fields_dict.available_slots.$wrapper;
// disable dialog action initially
d.get_primary_btn().attr('disabled', true);
var slot_details = data.slot_details;
var slot_html = "";
var duration = frm.doc.duration | 0;
$.each(slot_details, function(i, slot_detail){
slot_html = slot_html + `<label>${slot_detail['slot_name']}</label>`;
slot_html = slot_html + `<br/>` + slot_detail['avail_slot'].map(slot => {
let disabled = '';
let start_str = slot.from_time;
let slot_start_time = moment(slot.from_time, 'HH:mm:ss');
let slot_to_time = moment(slot.to_time, 'HH:mm:ss');
let interval = (slot_to_time - slot_start_time)/60000 | 0;
// iterate in all booked appointments, update the start time and duration
slot_detail['appointments'].forEach(function(booked) {
let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
let end_time = booked_moment.clone().add(booked.duration, 'minutes');
// Deal with 0 duration appointments
if(booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)){
if(booked.duration == 0){
disabled = 'disabled="disabled"';
return false;
}
}
// Check for overlaps considering appointment duration
if(slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)){
// There is an overlap
disabled = 'disabled="disabled"';
return false;
}
});
return `<button class="btn btn-default"
data-name=${start_str}
data-duration=${duration || interval}
data-service-unit="${slot_detail['service_unit'] || ''}"
style="margin: 0 10px 10px 0; width: 72px;" ${disabled}>
${start_str.substring(0, start_str.length - 3)}
</button>`;
}).join("");
slot_html = slot_html + `<br/>`;
});
$wrapper
.css('margin-bottom', 0)
.addClass('text-center')
.html(slot_html);
// blue button when clicked
$wrapper.on('click', 'button', function() {
var $btn = $(this);
$wrapper.find('button').removeClass('btn-primary');
$wrapper.find('button').removeClass('btn-selected-slot');
$btn.addClass('btn-primary');
$btn.addClass('btn-selected-slot');
// enable dialog action
d.get_primary_btn().attr('disabled', null);
});
d.show();
}
}, },
onload:function(frm){ onload:function(frm){
if(frm.is_new()) { if(frm.is_new()) {
@ -223,6 +117,176 @@ frappe.ui.form.on('Patient Appointment', {
} }
}); });
var check_and_set_availability = function(frm) {
var selected_slot = null;
var service_unit = null;
var duration = null;
show_availability();
function show_empty_state(practitioner, appointment_date) {
frappe.msgprint({
title: __('Not Available'),
message: __("Healthcare Practitioner {0} not available on {1}", [practitioner.bold(), appointment_date.bold()]),
indicator: 'red'
});
}
function show_availability() {
let selected_practitioner = '';
var d = new frappe.ui.Dialog({
title: __("Available slots"),
fields: [
{ fieldtype: 'Link', options: 'Medical Department', reqd:1, fieldname: 'department', label: 'Medical Department'},
{ fieldtype: 'Column Break'},
{ fieldtype: 'Link', options: 'Healthcare Practitioner', reqd:1, fieldname: 'practitioner', label: 'Healthcare Practitioner'},
{ fieldtype: 'Column Break'},
{ fieldtype: 'Date', reqd:1, fieldname: 'appointment_date', label: 'Date'},
{ fieldtype: 'Section Break'},
{ fieldtype: 'HTML', fieldname: 'available_slots'}
],
primary_action_label: __("Book"),
primary_action: function() {
frm.set_value('appointment_time', selected_slot);
frm.set_value('service_unit', service_unit || '');
frm.set_value('duration', duration);
frm.set_value('practitioner', d.get_value('practitioner'));
frm.set_value('department', d.get_value('department'));
frm.set_value('appointment_date', d.get_value('appointment_date'));
d.hide();
frm.enable_save();
frm.save();
frm.enable_save();
d.get_primary_btn().attr('disabled', true);
}
});
d.set_values({
'department': frm.doc.department,
'practitioner': frm.doc.practitioner,
'appointment_date': frm.doc.appointment_date
});
d.fields_dict["department"].df.onchange = () => {
d.set_values({
'practitioner': ''
});
var department = d.get_value('department');
if(department){
d.fields_dict.practitioner.get_query = function() {
return {
filters: {
"department": department
}
};
};
}
};
// disable dialog action initially
d.get_primary_btn().attr('disabled', true);
// Field Change Handler
var fd = d.fields_dict;
d.fields_dict["appointment_date"].df.onchange = () => {
show_slots(d, fd);
};
d.fields_dict["practitioner"].df.onchange = () => {
if(d.get_value('practitioner') && d.get_value('practitioner') != selected_practitioner){
selected_practitioner = d.get_value('practitioner');
show_slots(d, fd);
}
};
d.show();
}
function show_slots(d, fd) {
if (d.get_value('appointment_date') && d.get_value('practitioner')){
fd.available_slots.html("");
frappe.call({
method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_availability_data',
args: {
practitioner: d.get_value('practitioner'),
date: d.get_value('appointment_date')
},
callback: (r) => {
var data = r.message;
if(data.slot_details.length > 0) {
var $wrapper = d.fields_dict.available_slots.$wrapper;
// make buttons for each slot
var slot_details = data.slot_details;
var slot_html = "";
for (let i = 0; i < slot_details.length; i++) {
slot_html = slot_html + `<label>${slot_details[i].slot_name}</label>`;
slot_html = slot_html + `<br/>` + slot_details[i].avail_slot.map(slot => {
let disabled = '';
let start_str = slot.from_time;
let slot_start_time = moment(slot.from_time, 'HH:mm:ss');
let slot_to_time = moment(slot.to_time, 'HH:mm:ss');
let interval = (slot_to_time - slot_start_time)/60000 | 0;
// iterate in all booked appointments, update the start time and duration
slot_details[i].appointments.forEach(function(booked) {
let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
let end_time = booked_moment.clone().add(booked.duration, 'minutes');
// Deal with 0 duration appointments
if(booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)){
if(booked.duration == 0){
disabled = 'disabled="disabled"';
return false;
}
}
// Check for overlaps considering appointment duration
if(slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)){
// There is an overlap
disabled = 'disabled="disabled"';
return false;
}
});
return `<button class="btn btn-default"
data-name=${start_str}
data-duration=${interval}
data-service-unit="${slot_details[i].service_unit || ''}"
style="margin: 0 10px 10px 0; width: 72px;" ${disabled}>
${start_str.substring(0, start_str.length - 3)}
</button>`;
}).join("");
slot_html = slot_html + `<br/>`;
}
$wrapper
.css('margin-bottom', 0)
.addClass('text-center')
.html(slot_html);
// blue button when clicked
$wrapper.on('click', 'button', function() {
var $btn = $(this);
$wrapper.find('button').removeClass('btn-primary');
$btn.addClass('btn-primary');
selected_slot = $btn.attr('data-name');
service_unit = $btn.attr('data-service-unit');
duration = $btn.attr('data-duration');
// enable dialog action
d.get_primary_btn().attr('disabled', null);
});
}else {
// fd.available_slots.html("Please select a valid date.".bold())
show_empty_state(d.get_value('practitioner'), d.get_value('appointment_date'));
}
},
freeze: true,
freeze_message: __("Fetching records......")
});
}else{
fd.available_slots.html("Appointment date and Healthcare Practitioner are Mandatory".bold());
}
}
};
var get_procedure_prescribed = function(frm){ var get_procedure_prescribed = function(frm){
if(frm.doc.patient){ if(frm.doc.patient){
frappe.call({ frappe.call({

View File

@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext" source_link = "https://github.com/frappe/erpnext"
develop_version = '12.x.x-develop' develop_version = '12.x.x-develop'
staging_version = '11.0.3-beta.24' staging_version = '11.0.3-beta.30'
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"

View File

@ -36,10 +36,13 @@ class EmployeeBenefitClaim(Document):
frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits)) frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits))
def validate_max_benefit_for_component(self, payroll_period): def validate_max_benefit_for_component(self, payroll_period):
claimed_amount = self.claimed_amount if self.max_amount_eligible:
claimed_amount += get_previous_claimed_amount(self.employee, payroll_period, component = self.earning_component) claimed_amount = self.claimed_amount
if claimed_amount > self.max_amount_eligible: claimed_amount += get_previous_claimed_amount(self.employee,
frappe.throw(_("Maximum amount eligible for the component {0} exceeds {1}").format(self.earning_component, self.max_amount_eligible)) payroll_period, component = self.earning_component)
if claimed_amount > self.max_amount_eligible:
frappe.throw(_("Maximum amount eligible for the component {0} exceeds {1}")
.format(self.earning_component, self.max_amount_eligible))
def validate_non_pro_rata_benefit_claim(self, max_benefits, payroll_period): def validate_non_pro_rata_benefit_claim(self, max_benefits, payroll_period):
claimed_amount = self.claimed_amount claimed_amount = self.claimed_amount
@ -75,12 +78,17 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct):
total_pro_rata_max = 0 total_pro_rata_max = 0
benefit_amount_total = 0 benefit_amount_total = 0
for sal_struct_row in sal_struct.get("earnings"): for sal_struct_row in sal_struct.get("earnings"):
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"]) try:
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:
# show the error in tests?
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:
for sal_struct_row in sal_struct.get("earnings"): for sal_struct_row in sal_struct.get("earnings"):
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"])
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:
component_max = max_benefit_amount component_max = max_benefit_amount
benefit_amount = component_max * sal_struct.max_benefits / total_pro_rata_max benefit_amount = component_max * sal_struct.max_benefits / total_pro_rata_max

View File

@ -1,5 +1,5 @@
frappe.listview_settings['Employee Separation'] = { frappe.listview_settings['Employee Separation'] = {
add_fields: ["boarding_status", "employee_name", "date_of_joining", "department"], add_fields: ["boarding_status", "employee_name", "department"],
filters:[["boarding_status","=", "Pending"]], filters:[["boarding_status","=", "Pending"]],
get_indicator: function(doc) { get_indicator: function(doc) {
return [__(doc.boarding_status), frappe.utils.guess_colour(doc.boarding_status), "status,=," + doc.boarding_status]; return [__(doc.boarding_status), frappe.utils.guess_colour(doc.boarding_status), "status,=," + doc.boarding_status];

View File

@ -3,7 +3,9 @@
frappe.ui.form.on('Holiday List', { frappe.ui.form.on('Holiday List', {
refresh: function(frm) { refresh: function(frm) {
frm.set_value('total_holidays', frm.doc.holidays.length); if (frm.doc.holidays) {
frm.set_value('total_holidays', frm.doc.holidays.length);
}
}, },
from_date: function(frm) { from_date: function(frm) {
if (frm.doc.from_date && !frm.doc.to_date) { if (frm.doc.from_date && !frm.doc.to_date) {

View File

@ -14,7 +14,7 @@ frappe.ui.form.on("Job Offer", {
refresh: function (frm) { refresh: function (frm) {
if ((!frm.doc.__islocal) && (frm.doc.status == 'Accepted') if ((!frm.doc.__islocal) && (frm.doc.status == 'Accepted')
&& (frm.doc.docstatus === 1) && (!frm.doc.__onload.employee)) { && (frm.doc.docstatus === 1) && (!frm.doc.__onload || !frm.doc.__onload.employee)) {
frm.add_custom_button(__('Make Employee'), frm.add_custom_button(__('Make Employee'),
function () { function () {
erpnext.job_offer.make_employee(frm); erpnext.job_offer.make_employee(frm);
@ -22,7 +22,7 @@ frappe.ui.form.on("Job Offer", {
); );
} }
if(frm.doc.__onload.employee) { if(frm.doc.__onload && frm.doc.__onload.employee) {
frm.add_custom_button(__('Show Employee'), frm.add_custom_button(__('Show Employee'),
function () { function () {
frappe.set_route("Form", "Employee", frm.doc.__onload.employee); frappe.set_route("Form", "Employee", frm.doc.__onload.employee);

View File

@ -187,7 +187,7 @@ class LeaveApplication(Document):
self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type, self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type,
self.from_date, self.to_date, self.half_day, self.half_day_date) self.from_date, self.to_date, self.half_day, self.half_day_date)
if self.total_leave_days == 0: if self.total_leave_days <= 0:
frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave.")) frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
if not is_lwp(self.leave_type): if not is_lwp(self.leave_type):

View File

@ -1,3 +1,10 @@
frappe.listview_settings['Leave Application'] = { frappe.listview_settings['Leave Application'] = {
add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"] add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
get_indicator: function (doc) {
if (doc.status === "Approved") {
return [__("Approved"), "green", "status,=,Approved"];
} else if (doc.status === "Rejected") {
return [__("Rejected"), "red", "status,=,Rejected"];
}
}
}; };

View File

@ -177,7 +177,7 @@ var calculate_earning_total = function(doc, dt, dn, reset_amount) {
if(cint(tbl[i].depends_on_lwp) == 1) { if(cint(tbl[i].depends_on_lwp) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) / tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
cint(doc.total_working_days)*100)/100; cint(doc.total_working_days)*100)/100;
} else if(reset_amount) { } else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount; tbl[i].amount = tbl[i].default_amount;
} }
if(!tbl[i].do_not_include_in_total) { if(!tbl[i].do_not_include_in_total) {
@ -198,7 +198,7 @@ var calculate_ded_total = function(doc, dt, dn, reset_amount) {
for(var i = 0; i < tbl.length; i++){ for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_lwp) == 1) { if(cint(tbl[i].depends_on_lwp) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100; tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
} else if(reset_amount) { } else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount; tbl[i].amount = tbl[i].default_amount;
} }
if(!tbl[i].do_not_include_in_total) { if(!tbl[i].do_not_include_in_total) {

View File

@ -26,6 +26,7 @@ class TestSalarySlip(unittest.TestCase):
self.make_holiday_list() self.make_holiday_list()
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0)
def tearDown(self): def tearDown(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
@ -255,6 +256,9 @@ class TestSalarySlip(unittest.TestCase):
raise raise
frappe.db.sql("""delete from `tabAdditional Salary` where employee=%s""", (employee)) frappe.db.sql("""delete from `tabAdditional Salary` where employee=%s""", (employee))
# undelete fixture data
frappe.db.rollback()
def make_holiday_list(self): def make_holiday_list(self):
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company()) fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):

View File

@ -10,7 +10,8 @@
"rate": 5000.0, "rate": 5000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
}, },
{ {
"amount": 2000.0, "amount": 2000.0,
@ -21,7 +22,8 @@
"rate": 1000.0, "rate": 1000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -54,7 +56,8 @@
"rate": 5000.0, "rate": 5000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
}, },
{ {
"amount": 2000.0, "amount": 2000.0,
@ -65,7 +68,8 @@
"rate": 1000.0, "rate": 1000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -97,7 +101,8 @@
"rate": 5000.0, "rate": 5000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
}, },
{ {
"amount": 3000.0, "amount": 3000.0,
@ -109,7 +114,8 @@
"rate": 1000.0, "rate": 1000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -143,7 +149,8 @@
"rate": 3000.0, "rate": 3000.0,
"uom": "_Test UOM", "uom": "_Test UOM",
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC" "source_warehouse": "_Test Warehouse - _TC",
"allow_transfer_for_manufacture": 1
} }
], ],
"docstatus": 1, "docstatus": 1,

File diff suppressed because it is too large Load Diff

View File

@ -110,7 +110,7 @@ class JobCard(Document):
if self.items: if self.items:
self.transferred_qty = frappe.db.get_value('Stock Entry', {'job_card': self.name, self.transferred_qty = frappe.db.get_value('Stock Entry', {'job_card': self.name,
'work_order': self.work_order, 'docstatus': 1}, 'sum(fg_completed_qty)') 'work_order': self.work_order, 'docstatus': 1}, 'sum(fg_completed_qty)') or 0
self.db_set("transferred_qty", self.transferred_qty) self.db_set("transferred_qty", self.transferred_qty)

View File

@ -13,3 +13,11 @@ class ManufacturingSettings(Document):
def get_mins_between_operations(): def get_mins_between_operations():
return relativedelta(minutes=cint(frappe.db.get_single_value("Manufacturing Settings", return relativedelta(minutes=cint(frappe.db.get_single_value("Manufacturing Settings",
"mins_between_operations")) or 10) "mins_between_operations")) or 10)
@frappe.whitelist()
def is_material_consumption_enabled():
if not hasattr(frappe.local, 'material_consumption'):
frappe.local.material_consumption = cint(frappe.db.get_single_value('Manufacturing Settings',
'material_consumption'))
return frappe.local.material_consumption

View File

@ -514,7 +514,7 @@ def get_items_for_material_requests(doc, company=None):
doc = frappe._dict(json.loads(doc)) doc = frappe._dict(json.loads(doc))
doc['mr_items'] = [] doc['mr_items'] = []
po_items = doc['po_items'] if doc.get('po_items') else doc['items'] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
for data in po_items: for data in po_items:
warehouse = None warehouse = None
@ -533,10 +533,10 @@ def get_items_for_material_requests(doc, company=None):
else: else:
planned_qty = data.get('planned_qty') planned_qty = data.get('planned_qty')
bom_no = data.get('bom_no') bom_no = data.get('bom_no')
include_subcontracted_items = doc['include_subcontracted_items'] include_subcontracted_items = doc.get('include_subcontracted_items')
company = doc['company'] company = doc.get('company')
include_non_stock_items = doc['include_non_stock_items'] include_non_stock_items = doc.get('include_non_stock_items')
ignore_existing_ordered_qty = doc['ignore_existing_ordered_qty'] ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
if not planned_qty: if not planned_qty:
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx'))) frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))

View File

@ -48,7 +48,7 @@ class TestProductionPlan(unittest.TestCase):
filters = {'production_plan': pln.name}, as_list=1) filters = {'production_plan': pln.name}, as_list=1)
self.assertTrue(len(work_orders), len(pln.po_items)) self.assertTrue(len(work_orders), len(pln.po_items))
for name in material_requests: for name in material_requests:
mr = frappe.get_doc('Material Request', name[0]) mr = frappe.get_doc('Material Request', name[0])
mr.cancel() mr.cancel()
@ -164,7 +164,7 @@ def create_production_plan(**args):
mr_items = get_items_for_material_requests(pln.as_dict()) mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items: for d in mr_items:
pln.append('mr_items', d) pln.append('mr_items', d)
if not args.do_not_save: if not args.do_not_save:
pln.insert() pln.insert()
if not args.do_not_submit: if not args.do_not_submit:
@ -182,7 +182,7 @@ def make_bom(**args):
'quantity': args.quantity or 1, 'quantity': args.quantity or 1,
'company': args.company or '_Test Company' 'company': args.company or '_Test Company'
}) })
for item in args.raw_materials: for item in args.raw_materials:
item_doc = frappe.get_doc('Item', item) item_doc = frappe.get_doc('Item', item)
@ -191,8 +191,8 @@ def make_bom(**args):
'qty': 1, 'qty': 1,
'uom': item_doc.stock_uom, 'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom, 'stock_uom': item_doc.stock_uom,
'rate': item_doc.valuation_rate or args.rate 'rate': item_doc.valuation_rate or args.rate,
}) })
bom.insert(ignore_permissions=True) bom.insert(ignore_permissions=True)
bom.submit() bom.submit()

View File

@ -292,6 +292,7 @@ class TestWorkOrder(unittest.TestCase):
make_bom(item=fg_item, rate=1000, raw_materials = ['_Test FG Item', '_Test FG Non Stock Item']) make_bom(item=fg_item, rate=1000, raw_materials = ['_Test FG Item', '_Test FG Non Stock Item'])
wo = make_wo_order_test_record(production_item = fg_item) wo = make_wo_order_test_record(production_item = fg_item)
se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1)) se = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
se.insert() se.insert()
se.submit() se.submit()

View File

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

View File

@ -1,155 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.pages['production-analytics'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __('Production Analytics'),
single_column: true
});
new erpnext.ProductionAnalytics(wrapper);
frappe.breadcrumbs.add("Manufacturing");
}
erpnext.ProductionAnalytics = frappe.views.GridReportWithPlot.extend({
init: function(wrapper) {
this._super({
title: __("Production Analytics"),
parent: $(wrapper).find('.layout-main'),
page: wrapper.page,
doctypes: ["Item", "Company", "Fiscal Year", "Work Order"]
});
},
setup_columns: function() {
var std_columns = [
{id: "name", name: __("Status"), field: "name", width: 100}
];
this.make_date_range_columns();
this.columns = std_columns.concat(this.columns);
},
filters: [
{fieldtype:"Select", label: __("Company"), link:"Company", fieldname: "company",
default_value: __("Select Company...")},
{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
{fieldtype:"Select", label: __("Range"), fieldname: "range",
options:[{label: __("Daily"), value: "Daily"}, {label: __("Weekly"), value: "Weekly"},
{label: __("Monthly"), value: "Monthly"}, {label: __("Quarterly"), value: "Quarterly"},
{label: __("Yearly"), value: "Yearly"}]}
],
setup_filters: function() {
var me = this;
this._super();
this.trigger_refresh_on_change(["company"]);
this.trigger_refresh_on_change(["range"]);
this.show_zero_check();
},
init_filter_values: function() {
this._super();
this.filter_inputs.range.val('Monthly');
},
setup_chart: function() {
var me = this;
var chart_data = this.get_chart_data ? this.get_chart_data() : null;
const parent = this.wrapper.find('.chart')[0];
this.chart = new Chart(parent, {
height: 200,
data: chart_data,
type: 'line'
});
},
set_default_values: function() {
var values = {
from_date: frappe.datetime.str_to_user(frappe.datetime.add_months(frappe.datetime.now_datetime(),-12) ),
to_date: frappe.datetime.str_to_user(frappe.datetime.add_months(frappe.datetime.now_datetime(),1))
}
var me = this;
$.each(values, function(i, v) {
if(me.filter_inputs[i] && !me.filter_inputs[i].val())
me.filter_inputs[i].val(v);
})
},
prepare_data: function() {
// add Opening, Closing, Totals rows
// if filtered by account and / or voucher
var me = this;
var all_open_orders = {name:"All Work Orders", "id": "all-open-pos",
checked:true};
var not_started = {name:"Not Started", "id":"not-started-pos",
checked:true};
var overdue = {name:"Overdue (Not Started)", "id":"overdue-pos",
checked:true};
var pending = {name:"Pending", "id":"pending-pos",
checked:true};
var completed = {name:"Completed", "id":"completed-pos",
checked:true};
$.each(frappe.report_dump.data["Work Order"], function(i, d) {
var dateobj = frappe.datetime.str_to_obj(d.creation);
var date = frappe.datetime.str_to_user(d.creation.split(" ")[0]);
$.each(me.columns, function(i,col) {
if (i > 1){
var start_period = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(col.id));
var end_period = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(col.name));
var astart_date = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(d.actual_start_date));
var planned_start_date = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(d.planned_start_date));
var aend_date = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(d.actual_end_date));
var modified = frappe.datetime.user_to_obj(frappe.datetime.str_to_user(d.modified));
if (dateobj <= start_period || dateobj <= end_period) {
all_open_orders[col.field] = flt(all_open_orders[col.field]) + 1;
if(d.status=="Completed") {
if(aend_date < start_period || modified < start_period) {
completed[col.field] = flt(completed[col.field]) + 1;
}
else if (astart_date < start_period) {
pending[col.field] = flt(pending[col.field]) + 1;
}
else if (planned_start_date < start_period) {
overdue[col.field] = flt(overdue[col.field]) + 1;
} else {
not_started[col.field] = flt(not_started[col.field]) + 1;
}
}else if(d.status == "In Process")
{
if (astart_date < start_period || modified < start_period){
pending[col.field] = flt(pending[col.field]) + 1;
}else if (planned_start_date < start_period) {
overdue[col.field] = flt(overdue[col.field]) + 1;
}else{
not_started[col.field] = flt(not_started[col.field]) + 1;
}
}else if(d.status == "Not Started") {
if (planned_start_date < start_period){
overdue[col.field] = flt(overdue[col.field]) + 1;
}else{
not_started[col.field] = flt(not_started[col.field]) + 1;
}
}
}
}
});
});
if(me.columns.length < 30){
this.chart_area.toggle(true);
}else {
this.chart_area.toggle(false);
}
this.data = [all_open_orders, not_started, overdue, pending, completed];
}
});

View File

@ -1,26 +0,0 @@
{
"content": null,
"creation": "2012-09-21 20:15:16",
"docstatus": 0,
"doctype": "Page",
"icon": "fa fa-bar-chart",
"idx": 1,
"modified": "2017-02-20 17:33:05.913097",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "production-analytics",
"owner": "Administrator",
"page_name": "production-analytics",
"roles": [
{
"role": "Analytics"
},
{
"role": "Manufacturing Manager"
}
],
"script": null,
"standard": "Yes",
"style": null,
"title": "Production Analytics"
}

View File

@ -39,8 +39,5 @@ frappe.query_reports["Production Analytics"] = {
default: "Monthly", default: "Monthly",
reqd: 1 reqd: 1
} }
], ]
"formatter": function(value, row, column, data) {
return value;
}
} }

View File

@ -568,10 +568,10 @@ erpnext.patches.v11_0.remove_land_unit_icon
erpnext.patches.v11_0.add_default_dispatch_notification_template erpnext.patches.v11_0.add_default_dispatch_notification_template
erpnext.patches.v11_0.add_market_segments erpnext.patches.v11_0.add_market_segments
erpnext.patches.v11_0.add_sales_stages erpnext.patches.v11_0.add_sales_stages
execute:frappe.delete_doc("Page", "Sales Analytics") execute:frappe.delete_doc_if_exists("Page", "sales-analytics")
execute:frappe.delete_doc("Page", "Purchase Analytics") execute:frappe.delete_doc_if_exists("Page", "purchase-analytics")
execute:frappe.delete_doc("Page", "Stock Analytics") execute:frappe.delete_doc_if_exists("Page", "stock-analytics")
execute:frappe.delete_doc("Page", "Production Analytics") execute:frappe.delete_doc_if_exists("Page", "production-analytics")
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13
erpnext.patches.v11_0.drop_column_max_days_allowed erpnext.patches.v11_0.drop_column_max_days_allowed
erpnext.patches.v11_0.change_healthcare_desktop_icons erpnext.patches.v11_0.change_healthcare_desktop_icons

View File

@ -3,12 +3,18 @@ from frappe import _
def execute(): def execute():
""" assign lft and rgt appropriately """ """ assign lft and rgt appropriately """
if "Healthcare" not in frappe.get_active_domains():
return
frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit") frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit")
frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit_type") frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit_type")
company = frappe.get_value("Company", {"domain": "Healthcare"}, "name")
if not frappe.db.exists("Healthcare Service Unit", _('All Healthcare Service Units')): if company:
frappe.get_doc({ frappe.get_doc({
'doctype': 'Healthcare Service Unit', 'doctype': 'Healthcare Service Unit',
'healthcare_service_unit_name': _('All Healthcare Service Units'), 'healthcare_service_unit_name': _('All Healthcare Service Units'),
'is_group': 1 'is_group': 1,
'company': company
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)

View File

@ -32,7 +32,7 @@ def execute():
'user': user, 'user': user,
'default': 1 'default': 1
}) })
_doc.pos_profile_name = user + ' - ' + _doc.company
_doc.flags.ignore_validate = True _doc.flags.ignore_validate = True
_doc.flags.ignore_mandatory = True _doc.flags.ignore_mandatory = True
_doc.save() _doc.save()

View File

@ -11,14 +11,14 @@ def execute():
for pos in frappe.get_all(doctype, filters={'disabled': 0}): for pos in frappe.get_all(doctype, filters={'disabled': 0}):
doc = frappe.get_doc(doctype, pos.name) doc = frappe.get_doc(doctype, pos.name)
if not doc.user or doc.pos_profile_name: continue if not doc.user: continue
try: try:
doc.pos_profile_name = doc.user + ' - ' + doc.company pos_profile_name = doc.user + ' - ' + doc.company
doc.flags.ignore_validate = True doc.flags.ignore_validate = True
doc.flags.ignore_mandatory = True doc.flags.ignore_mandatory = True
doc.save() doc.save()
frappe.rename_doc(doctype, doc.name, doc.pos_profile_name, force=True) frappe.rename_doc(doctype, doc.name, pos_profile_name, force=True)
except frappe.LinkValidationError: except frappe.LinkValidationError:
frappe.db.set_value("POS Profile", doc.name, 'disabled', 1) frappe.db.set_value("POS Profile", doc.name, 'disabled', 1)

View File

@ -120,28 +120,3 @@ frappe.ui.form.on("Project Task", {
frm.trigger('tasks_refresh'); frm.trigger('tasks_refresh');
}, },
}); });
frappe.ui.form.on("Project", "validate", function (frm) {
if (frm.doc.collect_progress == 1) {
frappe.call({
method: "erpnext.projects.doctype.project.project.times_check",
args: {
"from1": frm.doc.from,
"to": frm.doc.to,
"first_email": frm.doc.first_email,
"second_email": frm.doc.second_email,
"daily_time_to_send": frm.doc.daily_time_to_send,
"weekly_time_to_send": frm.doc.weekly_time_to_send
},
callback: function (r) {
frm.set_value("from", r.message.from1);
frm.set_value("to", r.message.to);
frm.set_value("first_email", r.message.first_email);
frm.set_value("second_email", r.message.second_email);
frm.set_value("daily_time_to_send", r.message.daily_time_to_send);
frm.set_value("weekly_time_to_send", r.message.weekly_time_to_send);
}
});
}
});

View File

@ -427,23 +427,6 @@ def weekly():
project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Weekly") AND (`tabProject`.day_to_send = %s) AND (`tabProject`.weekly_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1""", today) project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Weekly") AND (`tabProject`.day_to_send = %s) AND (`tabProject`.weekly_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1""", today)
create_project_update(project) create_project_update(project)
@frappe.whitelist()
def times_check(from1, to, first_email, second_email, daily_time_to_send, weekly_time_to_send):
from1 = datetime.datetime.strptime(from1, "%H:%M:%S.%f")
from1 = from1.strftime("%H:00:00")
to = datetime.datetime.strptime(to, "%H:%M:%S.%f")
to = to.strftime("%H:00:00")
first_email = datetime.datetime.strptime(first_email, "%H:%M:%S.%f")
first_email = first_email.strftime("%H:00:00")
second_email = datetime.datetime.strptime(second_email, "%H:%M:%S.%f")
second_email = second_email.strftime("%H:00:00")
daily_time_to_send = datetime.datetime.strptime(daily_time_to_send, "%H:%M:%S.%f")
daily_time_to_send = daily_time_to_send.strftime("%H:00:00")
weekly_time_to_send = datetime.datetime.strptime(weekly_time_to_send, "%H:%M:%S.%f")
weekly_time_to_send = weekly_time_to_send.strftime("%H:00:00")
return {"from1": from1, "to": to, "first_email": first_email, "second_email": second_email,"daily_time_to_send": daily_time_to_send, "weekly_time_to_send": weekly_time_to_send}
#Call this function in order to generate the Project Update for a specific project #Call this function in order to generate the Project Update for a specific project
def create_project_update(project): def create_project_update(project):
data = [] data = []

View File

@ -80,15 +80,15 @@ frappe.ui.form.on("Task", {
} }
}, },
is_group: function(frm) { is_group: function (frm) {
frappe.call({ frappe.call({
method:"erpnext.projects.doctype.task.task.check_if_child_exists", method: "erpnext.projects.doctype.task.task.check_if_child_exists",
args: { args: {
name: frm.doc.name name: frm.doc.name
}, },
callback: function(r){ callback: function (r) {
if(r.message){ if (r.message.length > 0) {
frappe.msgprint(__('Cannot convert it to non-group. Child Tasks exist.')); frappe.msgprint(__(`Cannot convert it to non-group. The following child Tasks exist: ${r.message.join(", ")}.`));
frm.reload_doc(); frm.reload_doc();
} }
} }

View File

@ -2,12 +2,15 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json
from frappe.utils import getdate, date_diff, add_days, cstr import json
import frappe
from frappe import _, throw from frappe import _, throw
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
class CircularReferenceError(frappe.ValidationError): pass class CircularReferenceError(frappe.ValidationError): pass
class Task(NestedSet): class Task(NestedSet):
@ -157,8 +160,10 @@ class Task(NestedSet):
@frappe.whitelist() @frappe.whitelist()
def check_if_child_exists(name): def check_if_child_exists(name):
return frappe.db.sql("""select name from `tabTask` child_tasks = frappe.get_all("Task", filters={"parent_task": name})
where parent_task = %s""", name) child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks]
return child_tasks
def get_project(doctype, txt, searchfield, start, page_len, filters): def get_project(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
@ -237,4 +242,4 @@ def add_multiple_tasks(data, parent):
new_task.insert() new_task.insert()
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Task", ["lft", "rgt"]) frappe.db.add_index("Task", ["lft", "rgt"])

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
@ -444,39 +445,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"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_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -1032,7 +1000,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-28 14:44:32.912004", "modified": "2018-11-15 07:58:42.629845",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet", "name": "Timesheet",

View File

@ -346,6 +346,11 @@ body[data-route="pos"] .btn-more {
body[data-route="pos"] .collapse-btn { body[data-route="pos"] .collapse-btn {
cursor: pointer; cursor: pointer;
} }
@media (max-width: 767px) {
body[data-route="pos"] .page-actions {
max-width: 110px;
}
}
.price-info { .price-info {
position: absolute; position: absolute;
left: 0; left: 0;

View File

@ -26,8 +26,8 @@ $(document).bind('toolbar_setup', function() {
$('<li><a data-link-type="forum" href="https://discuss.erpnext.com" \ $('<li><a data-link-type="forum" href="https://discuss.erpnext.com" \
target="_blank">'+__('User Forum')+'</a></li>').insertBefore($help_menu); target="_blank">'+__('User Forum')+'</a></li>').insertBefore($help_menu);
$('<li><a href="https://gitter.im/frappe/erpnext" \ $('<li class="gitter-chat-link"><a href="https://gitter.im/frappe/erpnext" \
target="_blank">'+__('Chat')+'</a></li>').insertBefore($help_menu); target="_blank">'+__('Gitter Chat')+'</a></li>').insertBefore($help_menu);
$('<li><a href="https://github.com/frappe/erpnext/issues" \ $('<li><a href="https://github.com/frappe/erpnext/issues" \
target="_blank">'+__('Report an Issue')+'</a></li>').insertBefore($help_menu); target="_blank">'+__('Report an Issue')+'</a></li>').insertBefore($help_menu);

View File

@ -33,7 +33,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
item.margin_rate_or_amount = 0; item.margin_rate_or_amount = 0;
item.rate_with_margin = 0; item.rate_with_margin = 0;
} }
item.base_rate_with_margin = item.rate_with_margin * flt(this.frm.doc.conversion_rate); item.base_rate_with_margin = item.rate_with_margin * flt(frm.doc.conversion_rate);
cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.set_gross_profit(item);
cur_frm.cscript.calculate_taxes_and_totals(); cur_frm.cscript.calculate_taxes_and_totals();
@ -185,6 +185,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
is_return: function() {
if(!this.frm.doc.is_return && this.frm.doc.return_against) {
this.frm.set_value('return_against', '');
}
},
setup_quality_inspection: function() { setup_quality_inspection: function() {
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) { if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
return; return;

View File

@ -44,8 +44,8 @@ erpnext.timesheet.timer = function(frm, row, timestamp=0) {
}; };
erpnext.timesheet.control_timer = function(frm, dialog, row, timestamp=0) { erpnext.timesheet.control_timer = function(frm, dialog, row, timestamp=0) {
var $btn_start = $(".playpause .btn-start"); var $btn_start = dialog.$wrapper.find(".playpause .btn-start");
var $btn_complete = $(".playpause .btn-complete"); var $btn_complete = dialog.$wrapper.find(".playpause .btn-complete");
var interval = null; var interval = null;
var currentIncrement = timestamp; var currentIncrement = timestamp;
var initialised = row ? true : false; var initialised = row ? true : false;

32
erpnext/public/js/utils.js Normal file → Executable file
View File

@ -103,12 +103,32 @@ $.extend(erpnext, {
$.extend(erpnext.utils, { $.extend(erpnext.utils, {
set_party_dashboard_indicators: function(frm) { set_party_dashboard_indicators: function(frm) {
if(frm.doc.__onload && frm.doc.__onload.dashboard_info) { if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
var info = frm.doc.__onload.dashboard_info; var company_wise_info = frm.doc.__onload.dashboard_info;
frm.dashboard.add_indicator(__('Annual Billing: {0}', if(company_wise_info.length > 1) {
[format_currency(info.billing_this_year, info.currency)]), 'blue'); frm.dashboard.stats_area.removeClass('hidden');
frm.dashboard.add_indicator(__('Total Unpaid: {0}', frm.dashboard.stats_area_row.addClass('flex');
[format_currency(info.total_unpaid, info.currency)]), frm.dashboard.stats_area_row.css('flex-wrap', 'wrap');
info.total_unpaid ? 'orange' : 'green'); company_wise_info.forEach(function(info) {
frm.dashboard.stats_area_row.append(
'<div class="flex-column col-xs-6">'+
'<div style="margin-bottom:20px"><h6>'+info.company+'</h6></div>'+
'<div class="badge-link small" style="margin-bottom:10px">Annual Billing: '
+format_currency(info.billing_this_year, info.currency)+'</div>'+
'<div class="badge-link small" style="margin-bottom:20px">Total Unpaid: '
+format_currency(info.total_unpaid, info.currency)+'</div>'+
'</div>'
);
});
}
else if (company_wise_info.length === 1) {
frm.dashboard.stats_area.removeClass('hidden');
frm.dashboard.stats_area_row.append(
'</div><div class="col-xs-6 small" style="margin-bottom:10px">Annual Billing: <b>'
+format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)+'</b></div>' +
'<div class="col-xs-6 small" style="margin-bottom:10px">Total Unpaid: <b>'
+format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)+'</b></div>'
);
}
} }
}, },

View File

@ -320,7 +320,7 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
["attribute_value", "like", e.target.value + "%"] ["attribute_value", "like", e.target.value + "%"]
], ],
fields: ["attribute_value"], fields: ["attribute_value"],
parent: "Item" parent: "Item Attribute"
}, },
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {

View File

@ -11,14 +11,18 @@ from erpnext.accounts.utils import get_fiscal_year
from frappe.utils import today from frappe.utils import today
def setup(company=None, patch=True): def setup(company=None, patch=True):
setup_company_independent_fixtures()
if not patch:
update_address_template()
make_fixtures(company)
# TODO: for all countries
def setup_company_independent_fixtures():
make_custom_fields() make_custom_fields()
add_permissions() add_permissions()
add_custom_roles_for_reports() add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
add_print_formats() add_print_formats()
if not patch:
update_address_template()
make_fixtures(company)
def update_address_template(): def update_address_template():
with open(os.path.join(os.path.dirname(__file__), 'address_template.html'), 'r') as f: with open(os.path.join(os.path.dirname(__file__), 'address_template.html'), 'r') as f:
@ -350,12 +354,17 @@ def set_tax_withholding_category(company):
def set_tds_account(docs, company): def set_tds_account(docs, company):
abbr = frappe.get_value("Company", company, "abbr") abbr = frappe.get_value("Company", company, "abbr")
docs.extend([ parent_account = frappe.db.get_value("Account", filters = {"account_name": "Duties and Taxes", "company": company})
{ if parent_account:
"doctype": "Account", "account_name": "TDS Payable", "account_type": "Tax", docs.extend([
"parent_account": "Duties and Taxes - {0}".format(abbr), "company": company {
} "doctype": "Account",
]) "account_name": "TDS Payable",
"account_type": "Tax",
"parent_account": parent_account,
"company": company
}
])
def get_tds_details(accounts, fiscal_year): def get_tds_details(accounts, fiscal_year):
# bootstrap default tax withholding sections # bootstrap default tax withholding sections

View File

@ -206,7 +206,7 @@ cur_frm.cscript['Declare Order Lost'] = function(){
} }
frappe.ui.form.on("Quotation Item", "items_on_form_rendered", function(frm, cdt, cdn) { frappe.ui.form.on("Quotation Item", "items_on_form_rendered", "packed_items_on_form_rendered", function(frm, cdt, cdn) {
// enable tax_amount field if Actual // enable tax_amount field if Actual
}) })

View File

@ -32,7 +32,6 @@ class SalesOrder(SellingController):
def validate(self): def validate(self):
super(SalesOrder, self).validate() super(SalesOrder, self).validate()
self.validate_order_type() self.validate_order_type()
self.validate_delivery_date() self.validate_delivery_date()
self.validate_proj_cust() self.validate_proj_cust()
@ -342,9 +341,11 @@ class SalesOrder(SellingController):
delivered_qty += item.delivered_qty delivered_qty += item.delivered_qty
tot_qty += item.qty tot_qty += item.qty
self.db_set("per_delivered", flt(delivered_qty/tot_qty) * 100, if tot_qty != 0:
update_modified=False) self.db_set("per_delivered", flt(delivered_qty/tot_qty) * 100,
update_modified=False)
def set_indicator(self): def set_indicator(self):
"""Set indicator for portal""" """Set indicator for portal"""

View File

@ -125,6 +125,8 @@ def search_serial_or_batch_or_barcode_number(search_value):
if batch_no_data: if batch_no_data:
return batch_no_data return batch_no_data
return {}
def get_conditions(item_code, serial_no, batch_no, barcode): def get_conditions(item_code, serial_no, batch_no, barcode):
if serial_no or batch_no or barcode: if serial_no or batch_no or barcode:
return frappe.db.escape(item_code), "i.name = %(item_code)s" return frappe.db.escape(item_code), "i.name = %(item_code)s"

View File

@ -1 +0,0 @@
Trends of sales by Item, Item Group, Customer etc.

View File

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

View File

@ -1,246 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.pages['sales-analytics'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __('Sales Analytics'),
single_column: true
});
new erpnext.SalesAnalytics(wrapper);
frappe.breadcrumbs.add("Selling")
};
erpnext.SalesAnalytics = frappe.views.TreeGridReport.extend({
init: function(wrapper) {
this._super({
title: __("Sales Analytics"),
parent: $(wrapper).find('.layout-main'),
page: wrapper.page,
doctypes: ["Item", "Item Group", "Customer", "Customer Group", "Company", "Territory",
"Fiscal Year", "Sales Invoice", "Sales Invoice Item",
"Sales Order", "Sales Order Item[Sales Analytics]",
"Delivery Note", "Delivery Note Item[Sales Analytics]"],
tree_grid: { show: true }
});
this.tree_grids = {
"Customer Group": {
label: __("Customer Group / Customer"),
show: true,
item_key: "customer",
parent_field: "parent_customer_group",
formatter: function(item) {
return item.customer_name? item.customer_name + " (" + item.name + ")" : item.name;
}
},
"Customer": {
label: __("Customer"),
show: false,
item_key: "customer",
formatter: function(item) {
return item.customer_name? item.customer_name + " (" + item.name + ")" : item.name;
}
},
"Item Group": {
label: __("Item"),
show: true,
parent_field: "parent_item_group",
item_key: "item_code",
formatter: function(item) {
return item.name;
}
},
"Item": {
label: __("Item"),
show: false,
item_key: "item_code",
formatter: function(item) {
return item.name;
}
},
"Territory": {
label: __("Territory / Customer"),
show: true,
item_key: "customer",
parent_field: "parent_territory",
formatter: function(item) {
return item.customer_name? item.customer_name + " (" + item.name + ")" : item.name;
}
}
}
},
setup_columns: function() {
this.tree_grid = this.tree_grids[this.tree_type];
var std_columns = [
{id: "name", name: this.tree_grid.label, field: "name", width: 300},
{id: "total", name: "Total", field: "total", plot: false,
formatter: this.currency_formatter}
];
this.make_date_range_columns();
this.columns = std_columns.concat(this.columns);
},
filters: [
{fieldtype:"Select", fieldname: "tree_type", label: __("Tree Type"), options:["Customer Group", "Customer",
"Item Group", "Item", "Territory"],
filter: function(val, item, opts, me) {
return me.apply_zero_filter(val, item, opts, me);
}},
{fieldtype:"Select", fieldname: "based_on", label: __("Based On"), options:["Sales Invoice",
"Sales Order", "Delivery Note"]},
{fieldtype:"Select", fieldname: "value_or_qty", label: __("Value or Qty"),
options:[{label: __("Value"), value: "Value"}, {label: __("Quantity"), value: "Quantity"}]},
{fieldtype:"Date", fieldname: "from_date", label: __("From Date")},
{fieldtype:"Label", fieldname: "to", label: __("To")},
{fieldtype:"Date", fieldname: "to_date", label: __("To Date")},
{fieldtype:"Select", fieldname: "company", label: __("Company"), link:"Company",
default_value: __("Select Company...")},
{fieldtype:"Select", label: __("Range"), fieldname: "range",
options:[{label: __("Daily"), value: "Daily"}, {label: __("Weekly"), value: "Weekly"},
{label: __("Monthly"), value: "Monthly"}, {label: __("Quarterly"), value: "Quarterly"},
{label: __("Yearly"), value: "Yearly"}]}
],
setup_filters: function() {
var me = this;
this._super();
this.trigger_refresh_on_change(["value_or_qty", "tree_type", "based_on", "company"]);
this.show_zero_check();
},
init_filter_values: function() {
this._super();
this.filter_inputs.range.val('Monthly');
},
prepare_data: function() {
var me = this;
if (!this.tl) {
// add 'Not Set' Customer & Item
// (Customer / Item are not mandatory!!)
frappe.report_dump.data["Customer"].push({
name: "Not Set",
parent_customer_group: "All Customer Groups",
parent_territory: "All Territories",
id: "Not Set",
});
frappe.report_dump.data["Item"].push({
name: "Not Set",
parent_item_group: "All Item Groups",
id: "Not Set",
});
}
if (!this.tl || !this.tl[this.based_on]) {
this.make_transaction_list(this.based_on, this.based_on + " Item");
}
if(!this.data || me.item_type != me.tree_type) {
if(me.tree_type=='Customer') {
var items = frappe.report_dump.data["Customer"];
} if(me.tree_type=='Customer Group') {
var items = this.prepare_tree("Customer", "Customer Group");
} else if(me.tree_type=="Item Group") {
var items = this.prepare_tree("Item", "Item Group");
} else if(me.tree_type=="Item") {
var items = frappe.report_dump.data["Item"];
} else if(me.tree_type=="Territory") {
var items = this.prepare_tree("Customer", "Territory");
}
me.item_type = me.tree_type
me.parent_map = {};
me.item_by_name = {};
me.data = [];
$.each(items, function(i, v) {
var d = copy_dict(v);
me.data.push(d);
me.item_by_name[d.name] = d;
if(d[me.tree_grid.parent_field]) {
me.parent_map[d.name] = d[me.tree_grid.parent_field];
}
me.reset_item_values(d);
});
this.set_indent();
} else {
// otherwise, only reset values
$.each(this.data, function(i, d) {
me.reset_item_values(d);
});
}
this.prepare_balances();
if(me.tree_grid.show) {
this.set_totals(false);
this.update_groups();
} else {
this.set_totals(true);
}
},
prepare_balances: function() {
var me = this;
var from_date = frappe.datetime.str_to_obj(this.from_date);
var to_date = frappe.datetime.str_to_obj(this.to_date);
var is_val = this.value_or_qty == 'Value';
$.each(this.tl[this.based_on], function(i, tl) {
if (me.is_default('company') ? true : tl.company === me.company) {
var posting_date = frappe.datetime.str_to_obj(tl.posting_date);
if (posting_date >= from_date && posting_date <= to_date) {
var item = me.item_by_name[tl[me.tree_grid.item_key]] ||
me.item_by_name['Not Set'];
if(item){
item[me.column_map[tl.posting_date].field] += (is_val ? tl.base_net_amount : tl.qty);
}
}
}
});
},
update_groups: function() {
var me = this;
$.each(this.data, function(i, item) {
var parent = me.parent_map[item.name];
while(parent) {
var parent_group = me.item_by_name[parent];
$.each(me.columns, function(c, col) {
if (col.formatter == me.currency_formatter) {
parent_group[col.field] =
flt(parent_group[col.field])
+ flt(item[col.field]);
}
});
parent = me.parent_map[parent];
}
});
},
set_totals: function(sort) {
var me = this;
var checked = false;
$.each(this.data, function(i, d) {
d.total = 0.0;
$.each(me.columns, function(i, col) {
if(col.formatter==me.currency_formatter && !col.hidden && col.field!="total")
d.total += d[col.field];
if(d.checked) checked = true;
})
});
if(sort)this.data = this.data.sort(function(a, b) { return a.total < b.total; });
if(!this.checked) {
this.data[0].checked = true;
}
}
});

View File

@ -1,26 +0,0 @@
{
"creation": "2012-09-21 20:15:12.000000",
"docstatus": 0,
"doctype": "Page",
"icon": "fa fa-bar-chart",
"idx": 1,
"modified": "2013-07-11 14:43:59.000000",
"modified_by": "Administrator",
"module": "Selling",
"name": "sales-analytics",
"owner": "Administrator",
"page_name": "sales-analytics",
"roles": [
{
"role": "Analytics"
},
{
"role": "Sales Manager"
},
{
"role": "Maintenance Manager"
}
],
"standard": "Yes",
"title": "Sales Analytics"
}

View File

@ -6,8 +6,7 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"letter_head": "Test",
"modified": "2018-06-01 09:39:39.604944", "modified": "2018-06-01 09:39:39.604944",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
@ -30,4 +29,4 @@
"role": "Accounts User" "role": "Accounts User"
} }
] ]
} }

View File

@ -67,11 +67,8 @@ frappe.query_reports["Sales Analytics"] = {
reqd: 1 reqd: 1
} }
], ],
"formatter": function(value, row, column, data) { after_datatable_render: function(datatable_obj) {
if(!value){ $(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click();
value = 0
}
return value;
}, },
get_datatable_options(options) { get_datatable_options(options) {
return Object.assign(options, { return Object.assign(options, {
@ -79,11 +76,11 @@ frappe.query_reports["Sales Analytics"] = {
events: { events: {
onCheckRow: function(data) { onCheckRow: function(data) {
row_name = data[2].content; row_name = data[2].content;
row_values = data.slice(5).map(function (column) { length = data.length
row_values = data.slice(4,length-1).map(function (column) {
return column.content; return column.content;
}) })
entry = {
entry = {
'name':row_name, 'name':row_name,
'values':row_values 'values':row_values
} }
@ -109,15 +106,15 @@ frappe.query_reports["Sales Analytics"] = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets
} }
setTimeout(() => { setTimeout(() => {
frappe.query_report.chart.update(new_data) frappe.query_report.chart.update(new_data)
},200) }, 500)
setTimeout(() => { setTimeout(() => {
frappe.query_report.chart.draw(true); frappe.query_report.chart.draw(true);
}, 800) }, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },

View File

@ -166,7 +166,7 @@ class Analytics(object):
for entity, period_data in iteritems(self.entity_periodic_data): for entity, period_data in iteritems(self.entity_periodic_data):
row = { row = {
"entity": entity, "entity": entity,
"entity_name": self.entity_names.get(entity) "entity_name": self.entity_names.get(entity)
} }
total = 0 total = 0
for dummy, end_date in self.periodic_daterange: for dummy, end_date in self.periodic_daterange:
@ -177,7 +177,7 @@ class Analytics(object):
row["total"] = total row["total"] = total
self.data.append(row) self.data.append(row)
def get_rows_by_group(self): def get_rows_by_group(self):
self.get_periodic_data() self.get_periodic_data()
out = [] out = []
@ -185,7 +185,7 @@ class Analytics(object):
for d in reversed(self.group_entries): for d in reversed(self.group_entries):
row = { row = {
"entity": d.name, "entity": d.name,
"indent": self.depth_map.get(d.name) "indent": self.depth_map.get(d.name)
} }
total = 0 total = 0
for dummy, end_date in self.periodic_daterange: for dummy, end_date in self.periodic_daterange:
@ -212,11 +212,11 @@ class Analytics(object):
def get_period(self, posting_date): def get_period(self, posting_date):
if self.filters.range == 'Weekly': if self.filters.range == 'Weekly':
period = "Week " + str(posting_date.isocalendar()[1]) period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
elif self.filters.range == 'Monthly': elif self.filters.range == 'Monthly':
period = self.months[posting_date.month - 1] period = str(self.months[posting_date.month - 1]) + " " + str(posting_date.year)
elif self.filters.range == 'Quarterly': elif self.filters.range == 'Quarterly':
period = "Quarter " + str(((posting_date.month-1)//3)+1) period = "Quarter " + str(((posting_date.month-1)//3)+1) +" " + str(posting_date.year)
else: else:
year = get_fiscal_year(posting_date, company=self.filters.company) year = get_fiscal_year(posting_date, company=self.filters.company)
period = str(year[2]) period = str(year[2])
@ -275,12 +275,12 @@ class Analytics(object):
self.parent_child_map = frappe._dict(frappe.db.sql(""" select name, supplier_group from `tabSupplier`""")) self.parent_child_map = frappe._dict(frappe.db.sql(""" select name, supplier_group from `tabSupplier`"""))
def get_chart_data(self): def get_chart_data(self):
labels = [d.get("label") for d in self.columns[3:]] length = len(self.columns)
labels = [d.get("label") for d in self.columns[2:length-1]]
self.chart = { self.chart = {
"data": { "data": {
'labels': labels, 'labels': labels,
'datasets':[ 'datasets':[]
]
}, },
"type": "line" "type": "line"
} }

View File

@ -8,19 +8,23 @@ from erpnext.selling.report.sales_analytics.sales_analytics import execute
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
class TestAnalytics(unittest.TestCase): class TestAnalytics(unittest.TestCase):
def test_sales_analytics(self):
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
def tearDown(self): create_sales_orders()
frappe.db.sql(""" DELETE FROM `tabSales Order` """)
def test_by_entity(self): self.compare_result_for_customer()
create_sales_order() self.compare_result_for_customer_group()
self.compare_result_for_customer_based_on_quantity()
def compare_result_for_customer(self):
filters = { filters = {
'doc_type': 'Sales Order', 'doc_type': 'Sales Order',
'range': 'Monthly', 'range': 'Monthly',
'to_date': '2018-03-31', 'to_date': '2018-03-31',
'tree_type': 'Customer', 'tree_type': 'Customer',
'company': '_Test Company', 'company': '_Test Company 2',
'from_date': '2017-04-01', 'from_date': '2017-04-01',
'value_quantity': 'Value' 'value_quantity': 'Value'
} }
@ -31,153 +35,97 @@ class TestAnalytics(unittest.TestCase):
{ {
"entity": "_Test Customer 1", "entity": "_Test Customer 1",
"entity_name": "_Test Customer 1", "entity_name": "_Test Customer 1",
"apr": 0.0, "apr_2017": 0.0,
"may": 0.0, "may_2017": 0.0,
"jun": 0.0, "jun_2017": 0.0,
"jul": 0.0, "jul_2017": 0.0,
"aug": 0.0, "aug_2017": 0.0,
"sep": 0.0, "oct_2017": 0.0,
"oct": 0.0, "sep_2017": 0.0,
"nov": 0.0, "nov_2017": 0.0,
"dec": 0.0, "dec_2017": 0.0,
"jan": 0.0, "jan_2018": 0.0,
"feb": 2000.0, "feb_2018": 2000.0,
"mar": 0.0, "mar_2018": 0.0,
"total":2000.0 "total":2000.0
}, },
{
"entity": "_Test Customer 3",
"entity_name": "_Test Customer 3",
"apr": 0.0,
"may": 0.0,
"jun": 2000.0,
"jul": 1000.0,
"aug": 0.0,
"sep": 0.0,
"oct": 0.0,
"nov": 0.0,
"dec": 0.0,
"jan": 0.0,
"feb": 0.0,
"mar": 0.0,
"total": 3000.0
},
{ {
"entity": "_Test Customer 2", "entity": "_Test Customer 2",
"entity_name": "_Test Customer 2", "entity_name": "_Test Customer 2",
"apr": 0.0, "apr_2017": 0.0,
"may": 0.0, "may_2017": 0.0,
"jun": 0.0, "jun_2017": 0.0,
"jul": 0.0, "jul_2017": 0.0,
"aug": 0.0, "aug_2017": 0.0,
"sep": 1500.0, "sep_2017": 1500.0,
"oct": 1000.0, "oct_2017": 1000.0,
"nov": 0.0, "nov_2017": 0.0,
"dec": 0.0, "dec_2017": 0.0,
"jan": 0.0, "jan_2018": 0.0,
"feb": 0.0, "feb_2018": 0.0,
"mar": 0.0, "mar_2018": 0.0,
"total":2500.0 "total":2500.0
},
{
"entity": "_Test Customer 3",
"entity_name": "_Test Customer 3",
"apr_2017": 0.0,
"may_2017": 0.0,
"jun_2017": 2000.0,
"jul_2017": 1000.0,
"aug_2017": 0.0,
"sep_2017": 0.0,
"oct_2017": 0.0,
"nov_2017": 0.0,
"dec_2017": 0.0,
"jan_2018": 0.0,
"feb_2018": 0.0,
"mar_2018": 0.0,
"total": 3000.0
} }
] ]
self.assertEqual(expected_data, report[1]) result = sorted(report[1], key=lambda k: k['entity'])
self.assertEqual(expected_data, result)
def test_by_group(self):
create_sales_order()
def compare_result_for_customer_group(self):
filters = { filters = {
'doc_type': 'Sales Order', 'doc_type': 'Sales Order',
'range': 'Monthly', 'range': 'Monthly',
'to_date': '2018-03-31', 'to_date': '2018-03-31',
'tree_type': 'Customer Group', 'tree_type': 'Customer Group',
'company': '_Test Company', 'company': '_Test Company 2',
'from_date': '2017-04-01', 'from_date': '2017-04-01',
'value_quantity': 'Value' 'value_quantity': 'Value'
} }
report = execute(filters) report = execute(filters)
expected_data = [ expected_first_row = {
{ "entity": "All Customer Groups",
"entity": "All Customer Groups", "indent": 0,
"indent": 0, "apr_2017": 0.0,
"apr": 0.0, "may_2017": 0.0,
"may": 0.0, "jun_2017": 2000.0,
"jun": 2000.0, "jul_2017": 1000.0,
"jul": 1000.0, "aug_2017": 0.0,
"aug": 0.0, "sep_2017": 1500.0,
"sep": 1500.0, "oct_2017": 1000.0,
"oct": 1000.0, "nov_2017": 0.0,
"nov": 0.0, "dec_2017": 0.0,
"dec": 0.0, "jan_2018": 0.0,
"jan": 0.0, "feb_2018": 2000.0,
"feb": 2000.0, "mar_2018": 0.0,
"mar": 0.0, "total":7500.0
"total":7500.0 }
}, self.assertEqual(expected_first_row, report[1][0])
{
"entity": "Individual",
"indent": 1,
"apr": 0.0,
"may": 0.0,
"jun": 0.0,
"jul": 0.0,
"aug": 0.0,
"sep": 0.0,
"oct": 0.0,
"nov": 0.0,
"dec": 0.0,
"jan": 0.0,
"feb": 0.0,
"mar": 0.0,
"total": 0.0
},
{
"entity": "_Test Customer Group",
"indent": 1,
"apr": 0.0,
"may": 0.0,
"jun": 0.0,
"jul": 0.0,
"aug": 0.0,
"sep": 0.0,
"oct": 0.0,
"nov": 0.0,
"dec": 0.0,
"jan": 0.0,
"feb": 0.0,
"mar": 0.0,
"total":0.0
},
{
"entity": "_Test Customer Group 1",
"indent": 1,
"apr": 0.0,
"may": 0.0,
"jun": 0.0,
"jul": 0.0,
"aug": 0.0,
"sep": 0.0,
"oct": 0.0,
"nov": 0.0,
"dec": 0.0,
"jan": 0.0,
"feb": 0.0,
"mar": 0.0,
"total":0.0
}
]
self.assertEqual(expected_data, report[1])
def test_by_quantity(self):
create_sales_order()
def compare_result_for_customer_based_on_quantity(self):
filters = { filters = {
'doc_type': 'Sales Order', 'doc_type': 'Sales Order',
'range': 'Monthly', 'range': 'Monthly',
'to_date': '2018-03-31', 'to_date': '2018-03-31',
'tree_type': 'Customer', 'tree_type': 'Customer',
'company': '_Test Company', 'company': '_Test Company 2',
'from_date': '2017-04-01', 'from_date': '2017-04-01',
'value_quantity': 'Quantity' 'value_quantity': 'Quantity'
} }
@ -188,63 +136,93 @@ class TestAnalytics(unittest.TestCase):
{ {
"entity": "_Test Customer 1", "entity": "_Test Customer 1",
"entity_name": "_Test Customer 1", "entity_name": "_Test Customer 1",
"apr": 0.0, "apr_2017": 0.0,
"may": 0.0, "may_2017": 0.0,
"jun": 0.0, "jun_2017": 0.0,
"jul": 0.0, "jul_2017": 0.0,
"aug": 0.0, "aug_2017": 0.0,
"sep": 0.0, "sep_2017": 0.0,
"oct": 0.0, "oct_2017": 0.0,
"nov": 0.0, "nov_2017": 0.0,
"dec": 0.0, "dec_2017": 0.0,
"jan": 0.0, "jan_2018": 0.0,
"feb": 20.0, "feb_2018": 20.0,
"mar": 0.0, "mar_2018": 0.0,
"total":20.0 "total":20.0
}, },
{
"entity": "_Test Customer 3",
"entity_name": "_Test Customer 3",
"apr": 0.0,
"may": 0.0,
"jun": 20.0,
"jul": 10.0,
"aug": 0.0,
"sep": 0.0,
"oct": 0.0,
"nov": 0.0,
"dec": 0.0,
"jan": 0.0,
"feb": 0.0,
"mar": 0.0,
"total": 30.0
},
{ {
"entity": "_Test Customer 2", "entity": "_Test Customer 2",
"entity_name": "_Test Customer 2", "entity_name": "_Test Customer 2",
"apr": 0.0, "apr_2017": 0.0,
"may": 0.0, "may_2017": 0.0,
"jun": 0.0, "jun_2017": 0.0,
"jul": 0.0, "jul_2017": 0.0,
"aug": 0.0, "aug_2017": 0.0,
"sep": 15.0, "sep_2017": 15.0,
"oct": 10.0, "oct_2017": 10.0,
"nov": 0.0, "nov_2017": 0.0,
"dec": 0.0, "dec_2017": 0.0,
"jan": 0.0, "jan_2018": 0.0,
"feb": 0.0, "feb_2018": 0.0,
"mar": 0.0, "mar_2018": 0.0,
"total":25.0 "total":25.0
},
{
"entity": "_Test Customer 3",
"entity_name": "_Test Customer 3",
"apr_2017": 0.0,
"may_2017": 0.0,
"jun_2017": 20.0,
"jul_2017": 10.0,
"aug_2017": 0.0,
"sep_2017": 0.0,
"oct_2017": 0.0,
"nov_2017": 0.0,
"dec_2017": 0.0,
"jan_2018": 0.0,
"feb_2018": 0.0,
"mar_2018": 0.0,
"total": 30.0
} }
] ]
self.assertEqual(expected_data, report[1]) result = sorted(report[1], key=lambda k: k['entity'])
self.assertEqual(expected_data, result)
def create_sales_order(): def create_sales_orders():
frappe.set_user("Administrator") frappe.set_user("Administrator")
make_sales_order(qty=10, customer = "_Test Customer 1", transaction_date='2018-02-10') make_sales_order(company="_Test Company 2", qty=10,
make_sales_order(qty=10, customer = "_Test Customer 1", transaction_date='2018-02-15') customer = "_Test Customer 1",
make_sales_order(qty=15, customer = "_Test Customer 2", transaction_date='2017-09-23') transaction_date = '2018-02-10',
make_sales_order(qty=10, customer = "_Test Customer 2", transaction_date='2017-10-10') warehouse = 'Finished Goods - _TC2',
make_sales_order(qty=20, customer = "_Test Customer 3", transaction_date='2017-06-15') currency = 'EUR')
make_sales_order(qty=10, customer = "_Test Customer 3", transaction_date='2017-07-10')
make_sales_order(company="_Test Company 2",
qty=10, customer = "_Test Customer 1",
transaction_date = '2018-02-15',
warehouse = 'Finished Goods - _TC2',
currency = 'EUR')
make_sales_order(company = "_Test Company 2",
qty=10, customer = "_Test Customer 2",
transaction_date = '2017-10-10',
warehouse='Finished Goods - _TC2',
currency = 'EUR')
make_sales_order(company="_Test Company 2",
qty=15, customer = "_Test Customer 2",
transaction_date='2017-09-23',
warehouse='Finished Goods - _TC2',
currency = 'EUR')
make_sales_order(company="_Test Company 2",
qty=20, customer = "_Test Customer 3",
transaction_date='2017-06-15',
warehouse='Finished Goods - _TC2',
currency = 'EUR')
make_sales_order(company="_Test Company 2",
qty=10, customer = "_Test Customer 3",
transaction_date='2017-07-10',
warehouse='Finished Goods - _TC2',
currency = 'EUR')

View File

@ -97,7 +97,7 @@ class Company(NestedSet):
if not frappe.db.get_value("Department", {"company": self.name}): if not frappe.db.get_value("Department", {"company": self.name}):
from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures
install_post_company_fixtures(self.name) install_post_company_fixtures(frappe._dict({'company_name': self.name}))
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
self.create_default_cost_center() self.create_default_cost_center()
@ -363,7 +363,8 @@ def replace_abbr(company, old, new):
for d in doc: for d in doc:
_rename_record(d) _rename_record(d)
for dt in ["Warehouse", "Account", "Cost Center"]: for dt in ["Warehouse", "Account", "Cost Center", "Department", "Location",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
_rename_records(dt) _rename_records(dt)
frappe.db.commit() frappe.db.commit()

View File

@ -6,6 +6,9 @@ from __future__ import unicode_literals
import frappe, os, json import frappe, os, json
from frappe import _ from frappe import _
from frappe.desk.page.setup_wizard.setup_wizard import make_records
from frappe.utils import cstr, getdate
from erpnext.accounts.doctype.account.account import RootNotEditable
default_lead_sources = ["Existing Customer", "Reference", "Advertisement", default_lead_sources = ["Existing Customer", "Reference", "Advertisement",
"Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing",
@ -26,34 +29,6 @@ def install(country=None):
{ 'doctype': 'Domain', 'domain': 'Agriculture'}, { 'doctype': 'Domain', 'domain': 'Agriculture'},
{ 'doctype': 'Domain', 'domain': 'Non Profit'}, { 'doctype': 'Domain', 'domain': 'Non Profit'},
# Setup Progress
{'doctype': "Setup Progress", "actions": [
{"action_name": "Add Company", "action_doctype": "Company", "min_doc_count": 1, "is_completed": 1,
"domains": '[]' },
{"action_name": "Set Sales Target", "action_doctype": "Company", "min_doc_count": 99,
"action_document": frappe.defaults.get_defaults().get("company") or '',
"action_field": "monthly_sales_target", "is_completed": 0,
"domains": '["Manufacturing", "Services", "Retail", "Distribution"]' },
{"action_name": "Add Customers", "action_doctype": "Customer", "min_doc_count": 1, "is_completed": 0,
"domains": '["Manufacturing", "Services", "Retail", "Distribution"]' },
{"action_name": "Add Suppliers", "action_doctype": "Supplier", "min_doc_count": 1, "is_completed": 0,
"domains": '["Manufacturing", "Services", "Retail", "Distribution"]' },
{"action_name": "Add Products", "action_doctype": "Item", "min_doc_count": 1, "is_completed": 0,
"domains": '["Manufacturing", "Services", "Retail", "Distribution"]' },
{"action_name": "Add Programs", "action_doctype": "Program", "min_doc_count": 1, "is_completed": 0,
"domains": '["Education"]' },
{"action_name": "Add Instructors", "action_doctype": "Instructor", "min_doc_count": 1, "is_completed": 0,
"domains": '["Education"]' },
{"action_name": "Add Courses", "action_doctype": "Course", "min_doc_count": 1, "is_completed": 0,
"domains": '["Education"]' },
{"action_name": "Add Rooms", "action_doctype": "Room", "min_doc_count": 1, "is_completed": 0,
"domains": '["Education"]' },
{"action_name": "Add Users", "action_doctype": "User", "min_doc_count": 4, "is_completed": 0,
"domains": '[]' },
{"action_name": "Add Letterhead", "action_doctype": "Letter Head", "min_doc_count": 1, "is_completed": 0,
"domains": '[]' }
]},
# address template # address template
{'doctype':"Address Template", "country": country}, {'doctype':"Address Template", "country": country},
@ -124,8 +99,10 @@ def install(country=None):
{'doctype': 'Designation', 'designation_name': _('Designer')}, {'doctype': 'Designation', 'designation_name': _('Designer')},
{'doctype': 'Designation', 'designation_name': _('Researcher')}, {'doctype': 'Designation', 'designation_name': _('Researcher')},
# territory # territory: with two default territories, one for home country and one named Rest of the World
{'doctype': 'Territory', 'territory_name': _('All Territories'), 'is_group': 1, 'name': _('All Territories'), 'parent_territory': ''}, {'doctype': 'Territory', 'territory_name': _('All Territories'), 'is_group': 1, 'name': _('All Territories'), 'parent_territory': ''},
{'doctype': 'Territory', 'territory_name': country.replace("'", ""), 'is_group': 0, 'parent_territory': _('All Territories')},
{'doctype': 'Territory', 'territory_name': _("Rest Of The World"), 'is_group': 0, 'parent_territory': _('All Territories')},
# customer group # customer group
{'doctype': 'Customer Group', 'customer_group_name': _('All Customer Groups'), 'is_group': 1, 'name': _('All Customer Groups'), 'parent_customer_group': ''}, {'doctype': 'Customer Group', 'customer_group_name': _('All Customer Groups'), 'is_group': 1, 'name': _('All Customer Groups'), 'parent_customer_group': ''},
@ -231,7 +208,22 @@ def install(country=None):
# Share Management # Share Management
{"doctype": "Share Type", "title": _("Equity")}, {"doctype": "Share Type", "title": _("Equity")},
{"doctype": "Share Type", "title": _("Preference")} {"doctype": "Share Type", "title": _("Preference")},
# Market Segments
{"doctype": "Market Segment", "market_segment": _("Lower Income")},
{"doctype": "Market Segment", "market_segment": _("Middle Income")},
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
# Sales Stages
{"doctype": "Sales Stage", "stage_name": _("Prospecting")},
{"doctype": "Sales Stage", "stage_name": _("Qualification")},
{"doctype": "Sales Stage", "stage_name": _("Needs Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Value Proposition")},
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
] ]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types from erpnext.setup.setup_wizard.data.industry_type import get_industry_types
@ -260,7 +252,17 @@ def install(country=None):
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records
make_default_records() make_default_records()
make_fixture_records(records) make_records(records, True)
set_more_defaults()
# path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
# if os.path.exists(path.encode("utf-8")):
# frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))()
def set_more_defaults():
# Do more setup stuff that can be done here with no dependencies
# set default customer group and territory # set default customer group and territory
selling_settings = frappe.get_doc("Selling Settings") selling_settings = frappe.get_doc("Selling Settings")
@ -269,6 +271,33 @@ def install(country=None):
add_uom_data() add_uom_data()
# set no copy fields of an item doctype to item variant settings
doc = frappe.get_doc('Item Variant Settings')
doc.set_default_fields()
doc.save()
selling_settings = frappe.get_doc("Selling Settings")
selling_settings.cust_master_name = "Customer Name"
selling_settings.so_required = "No"
selling_settings.dn_required = "No"
selling_settings.allow_multiple_items = 1
selling_settings.sales_update_frequency = "Each Transaction"
selling_settings.save()
buying_settings = frappe.get_doc("Buying Settings")
buying_settings.supp_master_name = "Supplier Name"
buying_settings.po_required = "No"
buying_settings.pr_required = "No"
buying_settings.maintain_same_rate = 1
buying_settings.allow_multiple_items = 1
buying_settings.save()
hr_settings = frappe.get_doc("HR Settings")
hr_settings.emp_created_by = "Naming Series"
hr_settings.leave_approval_notification_template = _("Leave Approval Notification")
hr_settings.leave_status_notification_template = _("Leave Status Notification")
hr_settings.save()
def add_uom_data(): def add_uom_data():
# add UOMs # add UOMs
uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read()) uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read())
@ -306,7 +335,7 @@ def add_market_segments():
{"doctype": "Market Segment", "market_segment": _("Upper Income")} {"doctype": "Market Segment", "market_segment": _("Upper Income")}
] ]
make_fixture_records(records) make_records(records)
def add_sale_stages(): def add_sale_stages():
# Sale Stages # Sale Stages
@ -320,46 +349,143 @@ def add_sale_stages():
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
] ]
make_fixture_records(records)
def make_fixture_records(records): make_records(records)
from frappe.modules import scrub
for r in records:
doc = frappe.new_doc(r.get("doctype"))
doc.update(r)
# ignore mandatory for root def install_company(args):
parent_link_field = ("parent_" + scrub(doc.doctype)) records = [
if doc.meta.get_field(parent_link_field) and not doc.get(parent_link_field): # Fiscal Year
doc.flags.ignore_mandatory = True { "doctype": "Fiscal Year", 'year': get_fy_details(args.fy_start_date, args.fy_end_date), 'year_start_date': args.fy_start_date, 'year_end_date': args.fy_end_date },
try: # Company
doc.insert(ignore_permissions=True) {
except frappe.DuplicateEntryError as e: "doctype":"Company",
# pass DuplicateEntryError and continue 'company_name': args.company_name,
if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name: 'enable_perpetual_inventory': 1,
# make sure DuplicateEntryError is for the exact same doc and not a related doc 'abbr': args.company_abbr,
pass 'default_currency': args.currency,
else: 'country': args.country,
raise 'create_chart_of_accounts_based_on': 'Standard Template',
'chart_of_accounts': args.chart_of_accounts,
'domain': args.domain
}
]
def install_post_company_fixtures(company=None): make_records(records, True)
def install_post_company_fixtures(args=None):
records = [ records = [
# Department # Department
{'doctype': 'Department', 'department_name': _('All Departments'), 'is_group': 1, 'parent_department': ''}, {'doctype': 'Department', 'department_name': _('All Departments'), 'is_group': 1, 'parent_department': ''},
{'doctype': 'Department', 'department_name': _('Accounts'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Accounts'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Marketing'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Marketing'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Sales'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Sales'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Purchase'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Purchase'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Operations'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Operations'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Production'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Production'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Dispatch'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Dispatch'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Customer Service'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Customer Service'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Human Resources'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Human Resources'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Management'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Management'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Quality Management'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Quality Management'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Research & Development'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Research & Development'), 'parent_department': _('All Departments'), 'company': args.company_name},
{'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': company}, {'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name},
] ]
make_fixture_records(records) make_records(records)
def install_defaults(args=None):
records = [
# Price Lists
{ "doctype": "Price List", "price_list_name": _("Standard Buying"), "enabled": 1, "buying": 1, "selling": 0, "currency": args.currency },
{ "doctype": "Price List", "price_list_name": _("Standard Selling"), "enabled": 1, "buying": 0, "selling": 1, "currency": args.currency },
]
make_records(records)
# enable default currency
frappe.db.set_value("Currency", args.get("currency"), "enabled", 1)
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
current_fiscal_year = frappe.get_all("Fiscal Year")[0]
global_defaults.update({
'current_fiscal_year': current_fiscal_year.name,
'default_currency': args.get('currency'),
'default_company':args.get('company_name') ,
"country": args.get("country"),
})
global_defaults.save()
system_settings = frappe.get_doc("System Settings")
system_settings.email_footer_address = args.get("company_name")
system_settings.save()
domain_settings = frappe.get_single('Domain Settings')
domain_settings.set_active_domains(args.get('domains'))
stock_settings = frappe.get_doc("Stock Settings")
stock_settings.item_naming_by = "Item Code"
stock_settings.valuation_method = "FIFO"
stock_settings.default_warehouse = frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')})
stock_settings.stock_uom = _("Nos")
stock_settings.auto_indent = 1
stock_settings.auto_insert_price_list_rate_if_missing = 1
stock_settings.automatically_set_serial_nos_based_on_fifo = 1
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
stock_settings.save()
if args.bank_account:
company_name = args.company_name
bank_account_group = frappe.db.get_value("Account",
{"account_type": "Bank", "is_group": 1, "root_type": "Asset",
"company": company_name})
if bank_account_group:
bank_account = frappe.get_doc({
"doctype": "Account",
'account_name': args.bank_account,
'parent_account': bank_account_group,
'is_group':0,
'company': company_name,
"account_type": "Bank",
})
try:
doc = bank_account.insert()
frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False)
return doc
except RootNotEditable:
frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account))
except frappe.DuplicateEntryError:
# bank account same as a CoA entry
pass
# Now, with fixtures out of the way, onto concrete stuff
records = [
# Shopping cart: needs price lists
{
"doctype": "Shopping Cart Settings",
"enabled": 1,
'company': args.company_name,
# uh oh
'price_list': frappe.db.get_value("Price List", {"selling": 1}),
'default_customer_group': _("Individual"),
'quotation_series': "QTN-",
},
]
make_records(records, True)
def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year
if start_year == getdate(fy_end_date).year:
fy = cstr(start_year)
else:
fy = cstr(start_year) + '-' + cstr(start_year + 1)
return fy

View File

@ -5,7 +5,8 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from .operations import install_fixtures, taxes_setup, defaults_setup, company_setup, sample_data
from .operations import install_fixtures as fixtures, company_setup, taxes_setup, sample_data
def get_setup_stages(args=None): def get_setup_stages(args=None):
if frappe.db.sql("select name from tabCompany"): if frappe.db.sql("select name from tabCompany"):
@ -61,16 +62,10 @@ def get_setup_stages(args=None):
'fail_msg': _("Failed to setup post company fixtures") 'fail_msg': _("Failed to setup post company fixtures")
}, },
{ {
'fn': stage_three, 'fn': setup_defaults,
'args': args, 'args': args,
'fail_msg': _("Failed to set defaults") 'fail_msg': _("Failed to setup defaults")
} },
]
},
{
'status': _('Making website'),
'fail_msg': _('Failed to create website'),
'tasks': [
{ {
'fn': stage_four, 'fn': stage_four,
'args': args, 'args': args,
@ -93,38 +88,20 @@ def get_setup_stages(args=None):
return stages return stages
def setup_complete(args=None):
stage_fixtures(args)
setup_company(args)
setup_taxes(args)
setup_post_company_fixtures(args)
stage_three(args)
stage_four(args)
fin(args)
def stage_fixtures(args): def stage_fixtures(args):
install_fixtures.install(args.get("country")) fixtures.install(args.get('country'))
install_fixtures.add_market_segments()
install_fixtures.add_sale_stages()
def setup_company(args): def setup_company(args):
defaults_setup.create_price_lists(args) fixtures.install_company(args)
company_setup.create_fiscal_year_and_company(args)
company_setup.enable_shopping_cart(args)
company_setup.create_bank_account(args)
def setup_taxes(args): def setup_taxes(args):
taxes_setup.create_sales_tax(args) taxes_setup.create_sales_tax(args)
def setup_post_company_fixtures(args): def setup_post_company_fixtures(args):
install_fixtures.install_post_company_fixtures(args.get("company_name")) fixtures.install_post_company_fixtures(args)
def stage_three(args): def setup_defaults(args):
defaults_setup.create_employee_for_self(args) fixtures.install_defaults(frappe._dict(args))
defaults_setup.set_default_settings(args)
defaults_setup.create_territories()
defaults_setup.create_feed_and_todo()
defaults_setup.set_no_copy_fields_in_variant_settings()
def stage_four(args): def stage_four(args):
company_setup.create_website(args) company_setup.create_website(args)
@ -149,3 +126,14 @@ def make_sample_data(domains):
def login_as_first_user(args): def login_as_first_user(args):
if args.get("email") and hasattr(frappe.local, "login_manager"): if args.get("email") and hasattr(frappe.local, "login_manager"):
frappe.local.login_manager.login_as(args.get("email")) frappe.local.login_manager.login_as(args.get("email"))
# Only for programmatical use
def setup_complete(args=None):
stage_fixtures(args)
setup_company(args)
setup_taxes(args)
setup_post_company_fixtures(args)
setup_defaults(args)
stage_four(args)
fin(args)

View File

@ -223,6 +223,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
erpnext.setup_serial_no(); erpnext.setup_serial_no();
}, },
packed_items_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no();
},
close_delivery_note: function(doc){ close_delivery_note: function(doc){
this.update_status("Closed") this.update_status("Closed")
}, },

View File

@ -1,5 +1,5 @@
frappe.listview_settings['Delivery Note'] = { frappe.listview_settings['Delivery Note'] = {
add_fields: ["grand_total", "is_return", "per_billed", "status"], add_fields: ["grand_total", "is_return", "per_billed", "status", "currency"],
get_indicator: function (doc) { get_indicator: function (doc) {
if (cint(doc.is_return) == 1) { if (cint(doc.is_return) == 1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];

View File

@ -513,7 +513,7 @@ $.extend(erpnext.item, {
fields: ["attribute_value"], fields: ["attribute_value"],
limit_start: 0, limit_start: 0,
limit_page_length: 500, limit_page_length: 500,
parent: "Item", parent: "Item Attribute",
order_by: "idx" order_by: "idx"
} }
}).then((r) => { }).then((r) => {

View File

@ -32,6 +32,10 @@ class StockExistsForTemplate(frappe.ValidationError):
pass pass
class InvalidBarcode(frappe.ValidationError):
pass
class Item(WebsiteGenerator): class Item(WebsiteGenerator):
website = frappe._dict( website = frappe._dict(
page_title_field="item_name", page_title_field="item_name",
@ -502,19 +506,19 @@ class Item(WebsiteGenerator):
from stdnum import ean from stdnum import ean
if len(self.barcodes) > 0: if len(self.barcodes) > 0:
for item_barcode in self.barcodes: for item_barcode in self.barcodes:
options = frappe.get_meta("Item Barcode").get_options("barcode_type").split() options = frappe.get_meta("Item Barcode").get_options("barcode_type").split('\n')
if item_barcode.barcode: if item_barcode.barcode:
duplicate = frappe.db.sql( duplicate = frappe.db.sql(
"""select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name)) """select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
if duplicate: if duplicate:
frappe.throw(_("Barcode {0} already used in Item {1}").format( frappe.throw(_("Barcode {0} already used in Item {1}").format(
item_barcode.barcode, duplicate[0][0])) item_barcode.barcode, duplicate[0][0]), frappe.DuplicateEntryError)
item_barcode.barcode_type = "" if item_barcode.barcode_type not in options else item_barcode.barcode_type item_barcode.barcode_type = "" if item_barcode.barcode_type not in options else item_barcode.barcode_type
if item_barcode.barcode_type: if item_barcode.barcode_type and item_barcode.barcode_type.upper() in ('EAN', 'UPC-A', 'EAN-13', 'EAN-8'):
if not ean.is_valid(item_barcode.barcode): if not ean.is_valid(item_barcode.barcode):
frappe.throw(_("Barcode {0} is not a valid {1} code").format( frappe.throw(_("Barcode {0} is not a valid {1} code").format(
item_barcode.barcode, item_barcode.barcode_type)) item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
def validate_warehouse_for_reorder(self): def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory''' '''Validate Reorder level table for duplicate and conditional mandatory'''

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