Merge branch 'develop' into leaderboard-supplier

This commit is contained in:
Marica 2020-09-03 20:08:37 +05:30 committed by GitHub
commit 8d6b2d016f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 464 additions and 179 deletions

View File

@ -43,7 +43,7 @@
{
"hidden": 0,
"label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@ -98,7 +98,7 @@
"idx": 0,
"is_standard": 1,
"label": "Accounting",
"modified": "2020-06-19 12:42:44.054598",
"modified": "2020-09-03 10:37:07.865801",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@ -158,4 +158,4 @@
"type": "Dashboard"
}
]
}
}

View File

@ -10,13 +10,15 @@ frappe.ui.form.on('Process Deferred Accounting', {
}
};
});
},
if (frm.doc.company) {
type: function(frm) {
if (frm.doc.company && frm.doc.type) {
frm.set_query("account", function() {
return {
filters: {
'company': frm.doc.company,
'root_type': 'Liability',
'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset',
'is_group': 0
}
};

View File

@ -60,6 +60,7 @@
"reqd": 1
},
{
"depends_on": "eval: doc.type",
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
@ -73,9 +74,10 @@
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-02-06 18:18:09.852844",
"modified": "2020-09-03 18:07:02.463754",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Deferred Accounting",

View File

@ -71,7 +71,22 @@ frappe.query_reports["Budget Variance Report"] = {
fieldtype: "Check",
default: 0,
},
]
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname.includes('variance')) {
if (data[column.fieldname] < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
else if (data[column.fieldname] > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
}
return value;
}
}
erpnext.dimension_filters.forEach((dimension) => {

View File

@ -43,8 +43,11 @@ def execute(filters=None):
def validate_filters(filters, account_details):
if not filters.get('company'):
frappe.throw(_('{0} is mandatory').format(_('Company')))
if not filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
if not filters.get("from_date") and not filters.get("to_date"):
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
if filters.get("account") and not account_details.get(filters.account):
frappe.throw(_("Account {0} does not exists").format(filters.account))
@ -141,7 +144,7 @@ def get_gl_entries(filters, accounting_dimensions):
if filters and filters.get('cost_center'):
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
distributed_cost_center_query = """
UNION ALL
SELECT name as gl_entry,
@ -155,7 +158,7 @@ def get_gl_entries(filters, accounting_dimensions):
against_voucher_type,
against_voucher,
account_currency,
remarks, against,
remarks, against,
is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
FROM `tabGL Entry`,
(

View File

@ -1,24 +1,23 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-02-25 17:03:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:12:22.464240",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",
"owner": "Administrator",
"ref_doctype": "Sales Invoice",
"report_name": "Gross Profit",
"report_type": "Script Report",
"add_total_row": 1,
"creation": "2013-02-25 17:03:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2020-08-13 11:26:39.112352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",
"owner": "Administrator",
"ref_doctype": "Sales Invoice",
"report_name": "Gross Profit",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts Manager"
},
},
{
"role": "Accounts User"
}

View File

@ -11,6 +11,8 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
class TestRequestforQuotation(unittest.TestCase):
def test_quote_status(self):
@ -110,6 +112,23 @@ class TestRequestforQuotation(unittest.TestCase):
self.assertEqual(supplier_quotation.items[0].qty, 5)
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
def test_make_rfq_from_opportunity(self):
opportunity = make_opportunity(with_items=1)
supplier_data = get_supplier_data()
rfq = make_rfq(opportunity.name)
self.assertEqual(len(rfq.get("items")), len(opportunity.get("items")))
rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
for item in rfq.items:
item.warehouse = "_Test Warehouse - _TC"
for data in supplier_data:
rfq.append('suppliers', data)
rfq.status = 'Draft'
rfq.submit()
def make_request_for_quotation(**args):
"""
:param supplier_data: List containing supplier data

View File

@ -267,6 +267,9 @@ def make_quotation(source_name, target_doc=None):
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.conversion_factor = 1.0
doclist = get_mapped_doc("Opportunity", source_name, {
"Opportunity": {
"doctype": "Request for Quotation"
@ -277,7 +280,8 @@ def make_request_for_quotation(source_name, target_doc=None):
["name", "opportunity_item"],
["parent", "opportunity"],
["uom", "uom"]
]
],
"postprocess": update_item
}
}, target_doc)

View File

@ -82,7 +82,8 @@ def make_opportunity(**args):
if args.with_items:
opp_doc.append('items', {
"item_code": args.item_code or "_Test Item",
"qty": args.qty or 1
"qty": args.qty or 1,
"uom": "_Test UOM"
})
opp_doc.insert()

View File

@ -2,6 +2,17 @@
// For license information, please see license.txt
frappe.ui.form.on('Job Card', {
setup: function(frm) {
frm.set_query('operation', function() {
return {
query: 'erpnext.manufacturing.doctype.job_card.job_card.get_operations',
filters: {
'work_order': frm.doc.work_order
}
};
});
},
refresh: function(frm) {
frappe.flags.pause_job = 0;
frappe.flags.resume_job = 0;
@ -20,12 +31,60 @@ frappe.ui.form.on('Job Card', {
}
}
frm.trigger("toggle_operation_number");
if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
&& (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
},
operation: function(frm) {
frm.trigger("toggle_operation_number");
if (frm.doc.operation && frm.doc.work_order) {
frappe.call({
method: "erpnext.manufacturing.doctype.job_card.job_card.get_operation_details",
args: {
"work_order":frm.doc.work_order,
"operation":frm.doc.operation
},
callback: function (r) {
if (r.message) {
if (r.message.length == 1) {
frm.set_value("operation_id", r.message[0].name);
} else {
let args = [];
r.message.forEach((row) => {
args.push({ "label": row.idx, "value": row.name });
});
let description = __("Operation {0} added multiple times in the work order {1}",
[frm.doc.operation, frm.doc.work_order]);
frm.set_df_property("operation_row_number", "options", args);
frm.set_df_property("operation_row_number", "description", description);
}
frm.trigger("toggle_operation_number");
}
}
})
}
},
operation_row_number(frm) {
if (frm.doc.operation_row_number) {
frm.set_value("operation_id", frm.doc.operation_row_number);
}
},
toggle_operation_number(frm) {
frm.toggle_display("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
frm.toggle_reqd("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
},
prepare_timer_buttons: function(frm) {
frm.trigger("make_dashboard");
if (!frm.doc.job_started) {
@ -35,9 +94,9 @@ frappe.ui.form.on('Job Card', {
fieldname: 'employee'}, d => {
if (d.employee) {
frm.set_value("employee", d.employee);
} else {
frm.events.start_job(frm);
}
frm.events.start_job(frm);
}, __("Enter Value"), __("Start"));
} else {
frm.events.start_job(frm);
@ -82,9 +141,7 @@ frappe.ui.form.on('Job Card', {
frm.set_value('current_time' , 0);
}
frm.save("Save", () => {}, "", () => {
frm.doc.time_logs.pop(-1);
});
frm.save();
},
complete_job: function(frm, completed_time, completed_qty) {
@ -116,6 +173,8 @@ frappe.ui.form.on('Job Card', {
employee: function(frm) {
if (frm.doc.job_started && !frm.doc.current_time) {
frm.trigger("reset_timer");
} else {
frm.events.start_job(frm);
}
},

View File

@ -11,6 +11,7 @@
"bom_no",
"workstation",
"operation",
"operation_row_number",
"column_break_4",
"posting_date",
"company",
@ -291,11 +292,15 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "operation_row_number",
"fieldtype": "Select",
"label": "Operation Row Number"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-20 15:14:00.273441",
"modified": "2020-08-24 15:21:21.398267",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@ -347,7 +352,6 @@
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "operation",

View File

@ -15,10 +15,13 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings
class OverlapError(frappe.ValidationError): pass
class OperationMismatchError(frappe.ValidationError): pass
class JobCard(Document):
def validate(self):
self.validate_time_logs()
self.set_status()
self.validate_operation_id()
def validate_time_logs(self):
self.total_completed_qty = 0.0
@ -209,11 +212,10 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
field = "operation_id" if self.operation_id else "operation"
field = "operation_id"
data = frappe.get_all('Job Card',
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
filters = {"docstatus": 1, "work_order": self.work_order,
"workstation": self.workstation, field: self.get(field)})
filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
if data and len(data) > 0:
for_quantity = data[0].completed_qty
@ -226,14 +228,13 @@ class JobCard(Document):
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.workstation = %s and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.workstation, self.get(field)), as_dict=1)
and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.get(field)), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
work_order_field = "name" if field == "operation_id" else field
for data in wo.operations:
if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation:
if data.get("name") == self.get(field):
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
@ -306,6 +307,37 @@ class JobCard(Document):
if update_status:
self.db_set('status', self.status)
def validate_operation_id(self):
if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
work_order = frappe.bold(get_link_to_form("Work Order", self.work_order))
frappe.throw(_("Operation {0} does not belong to the work order {1}")
.format(frappe.bold(self.operation), work_order), OperationMismatchError)
@frappe.whitelist()
def get_operation_details(work_order, operation):
if work_order and operation:
return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
filters = {
"parent": work_order,
"operation": operation
}
)
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
if filters.get("work_order"):
args = {"parent": filters.get("work_order")}
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
return frappe.get_all("Work Order Operation",
filters = args,
fields = ["distinct operation as operation"],
limit_start = start,
limit_page_length = page_len,
order_by="idx asc", as_list=1)
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def update_item(obj, target, source_parent):

View File

@ -4,6 +4,72 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import random_string
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError
class TestJobCard(unittest.TestCase):
pass
def test_job_card(self):
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
bom, bom_item = data
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
job_cards = frappe.get_all('Job Card',
filters = {'work_order': work_order.name}, fields = ["operation_id", "name"])
if job_cards:
job_card = job_cards[0]
frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
doc = frappe.get_doc("Job Card", job_card.name)
doc.operation_id = "Test Data"
self.assertRaises(OperationMismatchError, doc.save)
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
bom, bom_item = data
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
job_cards = frappe.get_all('Job Card',
filters = {'work_order': work_order.name},
fields = ["operation_id", "workstation", "name", "for_quantity"])
job_card = job_cards[0]
if job_card:
workstation = frappe.db.get_value("Workstation",
{"name": ("not in", [job_card.workstation])}, "name")
if not workstation or job_card.workstation == workstation:
workstation = make_workstation(workstation_name=random_string(5)).name
doc = frappe.get_doc("Job Card", job_card.name)
doc.workstation = workstation
doc.append("time_logs", {
"from_time": "2009-01-01 12:06:25",
"to_time": "2009-01-01 12:37:25",
"time_in_mins": "31.00002",
"completed_qty": job_card.for_quantity
})
doc.submit()
completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
for d in job_cards:
frappe.delete_doc("Job Card", d.name)

View File

@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin

View File

@ -20,3 +20,18 @@ class TestWorkstation(unittest.TestCase):
"_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
def make_workstation(**args):
args = frappe._dict(args)
try:
doc = frappe.get_doc({
"doctype": "Workstation",
"workstation_name": args.workstation_name
})
doc.insert()
return doc
except frappe.DuplicateEntryError:
return frappe.get_doc("Workstation", args.workstation_name)

View File

@ -724,3 +724,4 @@ erpnext.patches.v12_0.update_state_code_for_daman_and_diu
erpnext.patches.v12_0.rename_lost_reason_detail
erpnext.patches.v13_0.drop_razorpay_payload_column
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports

View File

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

View File

@ -24,4 +24,8 @@ def execute():
if not frappe.db.exists('Warehouse Type', 'Transit'):
doc = frappe.new_doc('Warehouse Type')
doc.name = 'Transit'
doc.insert()
doc.insert()
frappe.reload_doc("stock", "doctype", "stock_entry_type")
frappe.delete_doc_if_exists("Stock Entry Type", "Send to Warehouse")
frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse")

View File

@ -503,11 +503,11 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
if(!r.exc && r.message) {
remove_empty_first_row(frm);
for ( var i=0; i< r.message.length; i++ ) {
for (var i=0; i< r.message.length; i++) {
var d = frm.add_child("items");
var item = r.message[i];
for ( var key in item) {
if ( !is_null(item[key]) ) {
for (var key in item) {
if (!is_null(item[key]) && key !== "doctype") {
d[key] = item[key];
}
}

View File

@ -73,6 +73,19 @@ def add_custom_roles_for_reports():
]
)).insert()
for report_name in ('HSN-wise-summary of outward supplies', 'GSTR-1', 'GSTR-2'):
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
frappe.get_doc(dict(
doctype='Custom Role',
report=report_name,
roles= [
dict(role='Accounts User'),
dict(role='Accounts Manager'),
dict(role='Auditor')
]
)).insert()
def add_permissions():
for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
add_permission(doctype, 'All', 0)

View File

@ -6,6 +6,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
shipping_address: function(frm) {
frm.trigger('get_tax_template');
},
supplier_address: function(frm) {
frm.trigger('get_tax_template');
},
tax_category: function(frm) {
frm.trigger('get_tax_template');
},

View File

@ -7,7 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-06-30 19:33:59.769385",
"modified": "2019-09-03 19:33:59.769385",
"modified_by": "Administrator",
"module": "Regional",
"name": "GSTR-1",
@ -16,15 +16,5 @@
"ref_doctype": "GL Entry",
"report_name": "GSTR-1",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
"roles": []
}

View File

@ -1,29 +1,19 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2018-01-29 12:59:55.650445",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-01-29 12:59:55.650445",
"modified_by": "Administrator",
"module": "Regional",
"name": "GSTR-2",
"owner": "Administrator",
"ref_doctype": "GL Entry",
"report_name": "GSTR-2",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2018-01-29 12:59:55.650445",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-09-03 12:59:55.650445",
"modified_by": "Administrator",
"module": "Regional",
"name": "GSTR-2",
"owner": "Administrator",
"ref_doctype": "GL Entry",
"report_name": "GSTR-2",
"report_type": "Script Report",
"roles": []
}

View File

@ -1,28 +1,18 @@
{
"add_total_row": 0,
"creation": "2018-04-26 10:49:29.159400",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-04-26 12:59:38.603649",
"modified_by": "Administrator",
"module": "Regional",
"name": "HSN-wise-summary of outward supplies",
"owner": "Administrator",
"ref_doctype": "Sales Invoice",
"report_name": "HSN-wise-summary of outward supplies",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
"add_total_row": 0,
"creation": "2018-04-26 10:49:29.159400",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-09-03 12:59:38.603649",
"modified_by": "Administrator",
"module": "Regional",
"name": "HSN-wise-summary of outward supplies",
"owner": "Administrator",
"ref_doctype": "Sales Invoice",
"report_name": "HSN-wise-summary of outward supplies",
"report_type": "Script Report",
"roles": []
}

View File

@ -396,13 +396,12 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
# form a list of emails and names to show to the user
credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
if not credit_controller_users:
credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
if not credit_controller_users_formatted:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
message = """Please contact any of the following users to extend the credit limits for {0}:
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users))
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))
# if the current user does not have permissions to override credit limit,
# prompt them to send out an email to the controller users
@ -427,7 +426,7 @@ def send_emails(args):
subject = (_("Credit limit reached for customer {0}").format(args.get('customer')))
message = (_("Credit limit has been crossed for customer {0} ({1}/{2})")
.format(args.get('customer'), args.get('customer_outstanding'), args.get('credit_limit')))
frappe.sendmail(recipients=[args.get('credit_controller_users_list')], subject=subject, message=message)
frappe.sendmail(recipients=args.get('credit_controller_users_list'), subject=subject, message=message)
def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None):
# Outstanding based on GL Entries

View File

@ -20,29 +20,28 @@ def get_funnel_data(from_date, to_date, company):
validate_filters(from_date, to_date, company)
active_leads = frappe.db.sql("""select count(*) from `tabLead`
where (date(`modified`) between %s and %s)
and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0]
where (date(`creation`) between %s and %s)
and company=%s""", (from_date, to_date, company))[0][0]
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
where (date(`creation`) between %s and %s)
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
and opportunity_from='Lead' and company=%s""", (from_date, to_date, company))[0][0]
quotations = frappe.db.sql("""select count(*) from `tabQuotation`
where docstatus = 1 and (date(`creation`) between %s and %s)
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
and (opportunity!="" or quotation_to="Lead") and company=%s""", (from_date, to_date, company))[0][0]
converted = frappe.db.sql("""select count(*) from `tabCustomer`
JOIN `tabLead` ON `tabLead`.name = `tabCustomer`.lead_name
WHERE (date(`tabCustomer`.creation) between %s and %s)
and `tabLead`.company=%s""", (from_date, to_date, company))[0][0]
sales_orders = frappe.db.sql("""select count(*) from `tabSales Order`
where docstatus = 1 and (date(`creation`) between %s and %s) and company=%s""", (from_date, to_date, company))[0][0]
return [
{ "title": _("Active Leads / Customers"), "value": active_leads, "color": "#B03B46" },
{ "title": _("Active Leads"), "value": active_leads, "color": "#B03B46" },
{ "title": _("Opportunities"), "value": opportunities, "color": "#F09C00" },
{ "title": _("Quotations"), "value": quotations, "color": "#006685" },
{ "title": _("Sales Orders"), "value": sales_orders, "color": "#00AD65" }
{ "title": _("Converted"), "value": converted, "color": "#00AD65" }
]
@frappe.whitelist()

View File

@ -63,13 +63,13 @@ def get_columns(filters, period_list, partner_doctype):
"label": _(partner_doctype),
"fieldtype": "Link",
"options": partner_doctype,
"width": 100
"width": 150
}, {
"fieldname": "item_group",
"label": _("Item Group"),
"fieldtype": "Link",
"options": "Item Group",
"width": 100
"width": 150
}]
for period in period_list:
@ -81,19 +81,19 @@ def get_columns(filters, period_list, partner_doctype):
"label": _("Target ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}, {
"fieldname": period.key,
"label": _("Achieved ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}, {
"fieldname": variance_key,
"label": _("Variance ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}])
columns.extend([{
@ -101,19 +101,19 @@ def get_columns(filters, period_list, partner_doctype):
"label": _("Total Target"),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}, {
"fieldname": "total_achieved",
"label": _("Total Achieved"),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}, {
"fieldname": "total_variance",
"label": _("Total Variance"),
"fieldtype": fieldtype,
"options": options,
"width": 100
"width": 150
}])
return columns
@ -154,10 +154,10 @@ def prepare_data(filters, sales_users_data, actual_data, date_field, period_list
if (r.get(sales_field) == d.parent and r.item_group == d.item_group and
period.from_date <= r.get(date_field) and r.get(date_field) <= period.to_date):
details[p_key] += r.get(qty_or_amount_field, 0)
details[variance_key] = details.get(target_key) - details.get(p_key)
details[variance_key] = details.get(p_key) - details.get(target_key)
details["total_achieved"] += details.get(p_key)
details["total_variance"] = details.get("total_target") - details.get("total_achieved")
details["total_variance"] = details.get("total_achieved") - details.get("total_target")
return rows

View File

@ -44,5 +44,20 @@ frappe.query_reports["Sales Partner Target Variance based on Item Group"] = {
options: "Quantity\nAmount",
default: "Quantity"
},
]
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname.includes('variance')) {
if (data[column.fieldname] < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
else if (data[column.fieldname] > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
}
return value;
}
}

View File

@ -44,5 +44,20 @@ frappe.query_reports["Sales Person Target Variance Based On Item Group"] = {
options: "Quantity\nAmount",
default: "Quantity"
},
]
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname.includes('variance')) {
if (data[column.fieldname] < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
else if (data[column.fieldname] > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
}
return value;
}
}

View File

@ -44,5 +44,20 @@ frappe.query_reports["Territory Target Variance Based On Item Group"] = {
options: "Quantity\nAmount",
default: "Quantity"
},
]
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname.includes('variance')) {
if (data[column.fieldname] < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
else if (data[column.fieldname] > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
}
return value;
}
}

View File

@ -258,6 +258,7 @@ class StockReconciliation(StockController):
sl_entries.append(args)
qty_after_transaction = 0
for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no])
@ -271,11 +272,19 @@ class StockReconciliation(StockController):
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
# If serial no exists in different warehouse
warehouse = previous_sle.get("warehouse", '') or row.warehouse
if not qty_after_transaction:
qty_after_transaction = get_stock_balance(row.item_code,
warehouse, self.posting_date, self.posting_time)
qty_after_transaction -= 1
new_args = args.copy()
new_args.update({
'actual_qty': -1,
'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
'qty_after_transaction': qty_after_transaction,
'warehouse': warehouse,
'valuation_rate': previous_sle.get("valuation_rate")
})

View File

@ -27,22 +27,25 @@
{% endif %}
</div>
{% endif %}
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
<div class="mt-3">
<a href="/cart"
class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
role="button"
>
{{ _("View in Cart") }}
</a>
<button
data-item-code="{{item_code}}"
class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
>
{{ _("Add to Cart") }}
</button>
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
<a href="/cart"
class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
role="button"
>
{{ _("View in Cart") }}
</a>
<button
data-item-code="{{item_code}}"
class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
>
{{ _("Add to Cart") }}
</button>
{% endif %}
{% if cart_settings.show_contact_us_button %}
{% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
{% endif %}
</div>
</div>

View File

@ -10,14 +10,11 @@
{{ _('Configure') }}
</button>
{% endif %}
{% if cart_settings.show_contact_us_button | int %}
<button class="btn btn-link btn-inquiry" data-item-code="{{ doc.name }}">
{{ _('Contact Us') }}
</button>
{% if cart_settings.show_contact_us_button %}
{% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
<script>
{% include "templates/generators/item/item_configure.js" %}
{% include "templates/generators/item/item_inquiry.js" %}
</script>
{% endif %}

View File

@ -0,0 +1,11 @@
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
{% set cart_settings = shopping_cart.cart_settings %}
{% if cart_settings.show_contact_us_button | int %}
<button class="btn btn-inquiry btn-primary-light" data-item-code="{{ doc.name }}">
{{ _('Contact Us') }}
</button>
{% endif %}
<script>
{% include "templates/generators/item/item_inquiry.js" %}
</script>
{% endif %}

View File

@ -282,9 +282,9 @@ balanced-match@^1.0.0:
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bl@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
version "3.0.1"
resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f"
integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==
dependencies:
readable-stream "^3.0.1"
@ -866,12 +866,12 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.3, inherits@~2.0.3:
inherits@2, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2.0.4, inherits@~2.0.1:
inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -1447,9 +1447,9 @@ readable-stream@2, readable-stream@~2.3.6:
util-deprecate "~1.0.1"
readable-stream@^3.0.1, readable-stream@^3.1.1:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606"
integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
@ -1505,9 +1505,9 @@ safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@~5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"