Merge branch 'version-12-hotfix' into batch-source-reference

This commit is contained in:
Marica
2020-09-02 20:24:07 +05:30
committed by GitHub
29 changed files with 659 additions and 132 deletions

View File

@@ -619,20 +619,12 @@ $.extend(erpnext.journal_entry, {
return { filters: filters };
},
reverse_journal_entry: function(frm) {
var me = frm.doc;
for(var i=0; i<me.accounts.length; i++) {
me.accounts[i].credit += me.accounts[i].debit;
me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit;
me.accounts[i].credit -= me.accounts[i].debit;
me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
me.accounts[i].reference_type = "Journal Entry";
me.accounts[i].reference_name = me.name
}
frm.copy_doc();
cur_frm.reload_doc();
}
reverse_journal_entry: function() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
frm: cur_frm
})
},
});
$.extend(erpnext.journal_entry, {

View File

@@ -1017,3 +1017,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
journal_entry.posting_date = nowdate()
journal_entry.inter_company_journal_entry_reference = name
return journal_entry.as_dict()
@frappe.whitelist()
def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
from frappe.model.mapper import get_mapped_doc
def update_accounts(source, target, source_parent):
target.reference_type = "Journal Entry"
target.reference_name = source_parent.name
doclist = get_mapped_doc("Journal Entry", source_name, {
"Journal Entry": {
"doctype": "Journal Entry",
"validation": {
"docstatus": ["=", 1]
}
},
"Journal Entry Account": {
"doctype": "Journal Entry Account",
"field_map": {
"account_currency": "account_currency",
"exchange_rate": "exchange_rate",
"debit_in_account_currency": "credit_in_account_currency",
"debit": "credit",
"credit_in_account_currency": "debit_in_account_currency",
"credit": "debit",
},
"postprocess": update_accounts,
},
}, target_doc, ignore_permissions=ignore_permissions)
return doclist

View File

@@ -139,6 +139,49 @@ class TestJournalEntry(unittest.TestCase):
self.assertFalse(gle)
def test_reverse_journal_entry(self):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
jv = make_journal_entry("_Test Bank USD - _TC",
"Sales - _TC", 100, exchange_rate=50, save=False)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.get("accounts")[1].exchange_rate = 1
jv.submit()
rjv = make_reverse_journal_entry(jv.name)
rjv.posting_date = nowdate()
rjv.submit()
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", rjv.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 100,
},
"Sales - _TC": {
"account_currency": "INR",
"debit": 5000,
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
}
}
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
jv = make_journal_entry("_Test Bank USD - _TC",

View File

@@ -29,27 +29,29 @@ class TestPOSProfile(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def make_pos_profile():
def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Profile`")
args = frappe._dict(args)
pos_profile = frappe.get_doc({
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"currency": "INR",
"company": args.company or "_Test Company",
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"currency": args.currency or "INR",
"doctype": "POS Profile",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"income_account": "Sales - _TC",
"name": "_Test POS Profile",
"expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
"income_account": args.income_account or "Sales - _TC",
"name": args.name or "_Test POS Profile",
"naming_series": "_T-POS Profile-",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory",
"selling_price_list": args.selling_price_list or "_Test Price List",
"territory": args.territory or "_Test Territory",
"customer_group": frappe.db.get_value('Customer Group', {'is_group': 0}, 'name'),
"warehouse": "_Test Warehouse - _TC",
"write_off_account": "_Test Write Off - _TC",
"write_off_cost_center": "_Test Write Off Cost Center - _TC"
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"write_off_account": args.write_off_account or "_Test Write Off - _TC",
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
})
if not frappe.db.exists("POS Profile", "_Test POS Profile"):
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
pos_profile.insert()
return pos_profile

View File

@@ -714,6 +714,64 @@ class TestSalesInvoice(unittest.TestCase):
self.pos_gl_entry(si, pos, 50)
def test_pos_returns_without_repayment(self):
pos_profile = make_pos_profile()
pos = create_sales_invoice(qty = 10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
pos.insert()
pos.submit()
pos_return = create_sales_invoice(is_return=1,
return_against=pos.name, qty=-5, do_not_save=True)
pos_return.is_pos = 1
pos_return.pos_profile = pos_profile.name
pos_return.insert()
pos_return.submit()
self.assertFalse(pos_return.is_pos)
self.assertFalse(pos_return.get('payments'))
def test_pos_returns_with_repayment(self):
pos_profile = make_pos_profile()
pos_profile.append('payments', {
'default': 1,
'mode_of_payment': 'Cash',
'amount': 0.0
})
pos_profile.save()
pos = create_sales_invoice(qty = 10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
pos.insert()
pos.submit()
pos_return = create_sales_invoice(is_return=1,
return_against=pos.name, qty=-5, do_not_save=True)
pos_return.is_pos = 1
pos_return.pos_profile = pos_profile.name
pos_return.insert()
pos_return.submit()
self.assertEqual(pos_return.get('payments')[0].amount, -500)
pos_profile.payments = []
pos_profile.save()
def test_pos_change_amount(self):
make_pos_profile()

View File

@@ -1064,6 +1064,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
$(frappe.render_template("pos_item", {
item_code: escape(obj.name),
item_price: item_price,
title: obj.name || obj.item_name,
item_name: obj.name === obj.item_name ? "" : obj.item_name,
item_image: obj.image,
item_stock: __('Stock Qty') + ": " + me.get_actual_qty(obj),
@@ -1545,6 +1546,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
$.each(this.frm.doc.items || [], function (i, d) {
$(frappe.render_template("pos_bill_item_new", {
item_code: escape(d.item_code),
title: d.item_code || d.item_name,
item_name: (d.item_name === d.item_code || !d.item_name) ? "" : ("<br>" + d.item_name),
qty: d.qty,
discount_percentage: d.discount_percentage || 0.0,

View File

@@ -391,7 +391,7 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type.lower(): party,
"company": company
"company": company
}
if tax_category:

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

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

@@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax
from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
class calculate_taxes_and_totals(object):
def __init__(self, doc):
@@ -208,7 +209,7 @@ class calculate_taxes_and_totals(object):
elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
elif tax.charge_type == "On Item Quantity":
inclusive_tax_amount_per_qty = flt(tax_rate)
@@ -524,7 +525,7 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against: return
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount'])
@@ -542,7 +543,7 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["paid_amount"])
change_amount = 0
if self.doc.doctype == "Sales Invoice":
if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
self.calculate_write_off_amount()
self.calculate_change_amount()
change_amount = self.doc.change_amount \
@@ -554,6 +555,9 @@ class calculate_taxes_and_totals(object):
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
self.doc.precision("outstanding_amount"))
if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
self.update_paid_amount_for_return(total_amount_to_pay)
def calculate_paid_amount(self):
paid_amount = base_paid_amount = 0.0
@@ -603,7 +607,7 @@ class calculate_taxes_and_totals(object):
base_rate_with_margin = 0.0
if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule:
for d in json.loads(item.pricing_rules):
for d in get_applied_pricing_rules(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
@@ -624,6 +628,27 @@ class calculate_taxes_and_totals(object):
def set_item_wise_tax_breakup(self):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def update_paid_amount_for_return(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
{'parent': self.doc.pos_profile, 'default': 1},
['mode_of_payment', 'type', 'account'], as_dict=1)
self.doc.payments = []
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'type': default_mode_of_payment.type,
'account': default_mode_of_payment.account,
'amount': total_amount_to_pay
})
else:
self.doc.is_pos = 0
self.doc.pos_profile = ''
self.calculate_paid_amount()
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
return

View File

@@ -149,7 +149,9 @@ var check_and_set_availability = function(frm) {
primary_action: function() {
frm.set_value('appointment_time', selected_slot);
frm.set_value('service_unit', service_unit || '');
frm.set_value('duration', duration);
if (!frm.doc.duration) {
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'));

View File

@@ -290,6 +290,7 @@ class PayrollEntry(Document):
"account": default_payroll_payable_account,
"credit_in_account_currency": flt(payable_amount, precision),
"party_type": '',
"cost_center": self.cost_center
})
journal_entry.set("accounts", accounts)

View File

@@ -54,7 +54,7 @@ class MaintenanceSchedule(TransactionBase):
email_map[d.sales_person] = sp.get_email_id()
except frappe.ValidationError:
no_email_sp.append(d.sales_person)
if no_email_sp:
frappe.msgprint(
frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
@@ -66,17 +66,17 @@ class MaintenanceSchedule(TransactionBase):
parent=%s""", (d.sales_person, d.item_code, self.name), as_dict=1)
for key in scheduled_date:
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
frappe.get_doc({
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
event = frappe.get_doc({
"doctype": "Event",
"owner": email_map.get(d.sales_person, self.owner),
"subject": description,
"description": description,
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
"event_type": "Private",
"ref_type": self.doctype,
"ref_name": self.name
}).insert(ignore_permissions=1)
})
event.add_participant(self.doctype, self.name)
event.insert(ignore_permissions=1)
frappe.db.set(self, 'status', 'Submitted')

View File

@@ -2,6 +2,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
from frappe.utils.data import get_datetime, add_days
import frappe
import unittest
@@ -9,4 +10,39 @@ import unittest
# test_records = frappe.get_test_records('Maintenance Schedule')
class TestMaintenanceSchedule(unittest.TestCase):
pass
def test_events_should_be_created_and_deleted(self):
ms = make_maintenance_schedule()
ms.generate_schedule()
ms.submit()
all_events = get_events(ms)
self.assertTrue(len(all_events) > 0)
ms.cancel()
events_after_cancel = get_events(ms)
self.assertTrue(len(events_after_cancel) == 0)
def get_events(ms):
return frappe.get_all("Event Participants", filters={
"reference_doctype": ms.doctype,
"reference_docname": ms.name,
"parenttype": "Event"
})
def make_maintenance_schedule():
ms = frappe.new_doc("Maintenance Schedule")
ms.company = "_Test Company"
ms.customer = "_Test Customer"
ms.transaction_date = get_datetime()
ms.append("items", {
"item_code": "_Test Item",
"start_date": get_datetime(),
"end_date": add_days(get_datetime(), 32),
"periodicity": "Weekly",
"no_of_visits": 4,
"sales_person": "Sales Team",
})
ms.insert(ignore_permissions=True)
return ms

View File

@@ -90,6 +90,7 @@ def update_latest_price_in_all_boms():
update_cost()
def replace_bom(args):
frappe.db.auto_commit_on_many_writes = 1
args = frappe._dict(args)
doc = frappe.get_doc("BOM Update Tool")
@@ -97,6 +98,8 @@ def replace_bom(args):
doc.new_bom = args.new_bom
doc.replace_bom()
frappe.db.auto_commit_on_many_writes = 0
def update_cost():
frappe.db.auto_commit_on_many_writes = 1
bom_list = get_boms_in_bottom_up_order()

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) {
if(frm.doc.docstatus == 0) {
@@ -24,12 +35,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) {
@@ -39,9 +98,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);
@@ -86,9 +145,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) {
@@ -120,6 +177,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

@@ -10,6 +10,7 @@
"bom_no",
"workstation",
"operation",
"operation_row_number",
"column_break_4",
"posting_date",
"company",
@@ -287,10 +288,15 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "operation_row_number",
"fieldtype": "Select",
"label": "Operation Row Number"
}
],
"is_submittable": 1,
"modified": "2020-03-27 13:36:35.417502",
"modified": "2020-08-24 15:21:21.398267",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@@ -342,7 +348,6 @@
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "operation",

View File

@@ -9,10 +9,13 @@ from frappe.utils import flt, time_diff_in_hours, get_datetime, time_diff, get_l
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
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
@@ -107,11 +110,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
@@ -124,14 +126,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):
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
@@ -204,6 +205,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,64 @@
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)
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_card = frappe.get_all('Job Card',
filters = {'work_order': work_order.name},
fields = ["operation_id", "workstation", "name", "for_quantity"])[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()

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

@@ -508,7 +508,7 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
var d = frm.add_child("items");
var item = r.message[i];
for ( var key in item) {
if ( !is_null(item[key]) ) {
if ( !is_null(item[key]) && key !== "doctype" ) {
d[key] = item[key];
}
}

View File

@@ -38,6 +38,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_total_advance(update_paid_amount);
}
if (this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_pos &&
this.frm.doc.is_return) {
this.update_paid_amount_for_return();
}
// Sales person's commission
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
this.calculate_commission();
@@ -653,23 +658,58 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}
},
set_default_payment: function(total_amount_to_pay, update_paid_amount){
update_paid_amount_for_return: function() {
var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance
- this.frm.doc.write_off_amount), precision("grand_total"));
} else {
var total_amount_to_pay = flt(
(flt(grand_total*this.frm.doc.conversion_rate, precision("grand_total"))
- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount),
precision("base_grand_total")
);
}
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
['mode_of_payment', 'account', 'type'], (value) => {
if (this.frm.is_dirty()) {
frappe.model.clear_table(this.frm.doc, 'payments');
if (value) {
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
row.mode_of_payment = value.mode_of_payment;
row.type = value.type;
row.account = value.account;
row.default = 1;
row.amount = total_amount_to_pay;
} else {
this.frm.set_value('is_pos', 1);
}
this.frm.refresh_fields();
}
}, 'Sales Invoice');
this.calculate_paid_amount();
},
set_default_payment: function(total_amount_to_pay, update_paid_amount) {
var me = this;
var payment_status = true;
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)){
$.each(this.frm.doc['payments'] || [], function(index, data){
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
$.each(this.frm.doc['payments'] || [], function(index, data) {
if(data.default && payment_status && total_amount_to_pay > 0) {
data.base_amount = flt(total_amount_to_pay, precision("base_amount"));
data.amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount"));
payment_status = false;
}else if(me.frm.doc.paid_amount){
} else if(me.frm.doc.paid_amount) {
data.amount = 0.0;
}
});
}
},
calculate_paid_amount: function(){
calculate_paid_amount: function() {
var me = this;
var paid_amount = 0.0;
var base_paid_amount = 0.0;

View File

@@ -1,7 +1,7 @@
<div class="pos-list-row pos-bill-item {{ selected_class }}" data-item-code="{{ item_code }}">
<div class="cell subject">
<!--<input class="list-row-checkbox" type="checkbox" data-name="{{item_code}}">-->
<a class="grey list-id" title="{{ item_name }}">{{ strip_html(__(item_name)) || item_code }}</a>
<a class="grey list-id" title="{{ title }}">{{ strip_html(__(title)) }}</a>
</div>
<div class="cell text-right">{%= qty %}</div>
<div class="cell text-right">{%= discount_percentage %}</div>

View File

@@ -1,12 +1,12 @@
<div class="pos-item-wrapper image-view-item" data-item-code="{{item_code}}">
<div class="image-view-header doclist-row">
<div class="list-value">
<a class="grey list-id" data-name="{{item_code}}" title="{{ item_name || item_code}}">{{item_name || item_code}}<br>({{ __(item_stock) }})</a>
<a class="grey list-id" data-name="{{item_code}}" title="{{ title }}">{{ title }}<br>({{ __(item_stock) }})</a>
</div>
</div>
<div class="image-view-body">
<a data-item-code="{{ item_code }}"
title="{{ item_name || item_code }}"
title="{{ title }}"
>
<div class="image-field"
style="

View File

@@ -4,7 +4,7 @@
frappe.provide("erpnext.utils");
erpnext.utils.get_party_details = function(frm, method, args, callback) {
if(!method) {
if (!method) {
method = "erpnext.accounts.party.get_party_details";
}
@@ -22,12 +22,12 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
}
}
if(!args) {
if((frm.doctype != "Purchase Order" && frm.doc.customer)
if (!args) {
if ((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
let party_type = "Customer";
if(frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
party_type = "Lead";
}
@@ -36,7 +36,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
party_type: party_type,
price_list: frm.doc.selling_price_list
};
} else if(frm.doc.supplier) {
} else if (frm.doc.supplier) {
args = {
party: frm.doc.supplier,
party_type: "Supplier",
@@ -78,13 +78,17 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
}
}
if(!args || !args.party) return;
if (!args || !args.party) return;
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
args.posting_date, args.party_type=="Customer" ? "customer": "supplier")) return;
}
if (!erpnext.utils.validate_mandatory(frm, "Company", frm.doc.company, args.party_type=="Customer" ? "customer": "supplier")) {
return;
}
args.currency = frm.doc.currency;
args.company = frm.doc.company;
args.doctype = frm.doc.doctype;
@@ -92,14 +96,14 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
method: method,
args: args,
callback: function(r) {
if(r.message) {
if (r.message) {
frm.supplier_tds = r.message.supplier_tds;
frm.updating_party_details = true;
frappe.run_serially([
() => frm.set_value(r.message),
() => {
frm.updating_party_details = false;
if(callback) callback();
if (callback) callback();
frm.refresh();
erpnext.utils.add_item(frm);
}
@@ -110,9 +114,9 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
}
erpnext.utils.add_item = function(frm) {
if(frm.is_new()) {
if (frm.is_new()) {
var prev_route = frappe.get_prev_route();
if(prev_route[1]==='Item' && !(frm.doc.items && frm.doc.items.length)) {
if (prev_route[1]==='Item' && !(frm.doc.items && frm.doc.items.length)) {
// add row
var item = frm.add_child('items');
frm.refresh_field('items');
@@ -124,23 +128,23 @@ erpnext.utils.add_item = function(frm) {
}
erpnext.utils.get_address_display = function(frm, address_field, display_field, is_your_company_address) {
if(frm.updating_party_details) return;
if (frm.updating_party_details) return;
if(!address_field) {
if(frm.doctype != "Purchase Order" && frm.doc.customer) {
if (!address_field) {
if (frm.doctype != "Purchase Order" && frm.doc.customer) {
address_field = "customer_address";
} else if(frm.doc.supplier) {
} else if (frm.doc.supplier) {
address_field = "supplier_address";
} else return;
}
if(!display_field) display_field = "address_display";
if(frm.doc[address_field]) {
if (!display_field) display_field = "address_display";
if (frm.doc[address_field]) {
frappe.call({
method: "frappe.contacts.doctype.address.address.get_address_display",
args: {"address_dict": frm.doc[address_field] },
callback: function(r) {
if(r.message) {
if (r.message) {
frm.set_value(display_field, r.message)
}
}
@@ -151,15 +155,15 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field,
};
erpnext.utils.set_taxes_from_address = function(frm, triggered_from_field, billing_address_field, shipping_address_field) {
if(frm.updating_party_details) return;
if (frm.updating_party_details) return;
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier",
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if (!erpnext.utils.validate_mandatory(frm, "Lead / Customer / Supplier",
frm.doc.customer || frm.doc.supplier || frm.doc.lead || frm.doc.party_name, triggered_from_field)) {
return;
}
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
frm.doc.posting_date || frm.doc.transaction_date, triggered_from_field)) {
return;
}
@@ -175,8 +179,8 @@ erpnext.utils.set_taxes_from_address = function(frm, triggered_from_field, billi
"shipping_address": frm.doc[shipping_address_field]
},
callback: function(r) {
if(!r.exc){
if(frm.doc.tax_category != r.message) {
if (!r.exc){
if (frm.doc.tax_category != r.message) {
frm.set_value("tax_category", r.message);
} else {
erpnext.utils.set_taxes(frm, triggered_from_field);
@@ -187,13 +191,17 @@ erpnext.utils.set_taxes_from_address = function(frm, triggered_from_field, billi
};
erpnext.utils.set_taxes = function(frm, triggered_from_field) {
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier",
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if (!erpnext.utils.validate_mandatory(frm, "Company", frm.doc.company, triggered_from_field)) {
return;
}
if (!erpnext.utils.validate_mandatory(frm, "Lead / Customer / Supplier",
frm.doc.customer || frm.doc.supplier || frm.doc.lead || frm.doc.party_name, triggered_from_field)) {
return;
}
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
frm.doc.posting_date || frm.doc.transaction_date, triggered_from_field)) {
return;
}
@@ -230,7 +238,7 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) {
"shipping_address": frm.doc.shipping_address_name
},
callback: function(r) {
if(r.message){
if (r.message){
frm.set_value("taxes_and_charges", r.message)
}
}
@@ -238,14 +246,14 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) {
};
erpnext.utils.get_contact_details = function(frm) {
if(frm.updating_party_details) return;
if (frm.updating_party_details) return;
if(frm.doc["contact_person"]) {
if (frm.doc["contact_person"]) {
frappe.call({
method: "frappe.contacts.doctype.contact.contact.get_contact_details",
args: {contact: frm.doc.contact_person },
callback: function(r) {
if(r.message)
if (r.message)
frm.set_value(r.message);
}
})
@@ -253,10 +261,10 @@ erpnext.utils.get_contact_details = function(frm) {
}
erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
if(!value) {
if (!value) {
frm.doc[trigger_on] = "";
refresh_field(trigger_on);
frappe.msgprint(__("Please enter {0} first", [label]));
frappe.throw({message:__("Please enter {0} first", [label]), title:__("Mandatory")});
return false;
}
return true;
@@ -271,12 +279,12 @@ erpnext.utils.get_shipping_address = function(frm, callback){
address: frm.doc.shipping_address
},
callback: function(r){
if(r.message){
if (r.message){
frm.set_value("shipping_address", r.message[0]) //Address title or name
frm.set_value("shipping_address_display", r.message[1]) //Address to be displayed on the page
}
if(callback){
if (callback){
return callback();
}
}

View File

@@ -46,5 +46,28 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = {
],
onload: (report) => {
fetch_gstins(report);
report.page.add_inner_button(__("Download JSON"), function () {
var filters = report.get_values();
frappe.call({
method: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.get_json',
args: {
data: report.data,
report_name: report.report_name,
filters: filters
},
callback: function(r) {
if (r.message) {
const args = {
cmd: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.download_json_file',
data: r.message.data,
report_name: r.message.report_name
};
open_url_post(frappe.request.url, args);
}
}
});
});
}
};

View File

@@ -4,11 +4,13 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
from frappe.utils import flt
from frappe.utils import flt, getdate, cstr
from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html
from six import iteritems
import json
from erpnext.regional.india.utils import get_gst_accounts
from erpnext.regional.report.gstr_1.gstr_1 import get_company_gstin_number
def execute(filters=None):
return _execute(filters)
@@ -141,7 +143,7 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
tax_details = frappe.db.sql("""
select
parent, description, item_wise_tax_detail,
parent, account_head, item_wise_tax_detail,
base_tax_amount_after_discount_amount
from `tab%s`
where
@@ -153,11 +155,11 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
""" % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions),
tuple([doctype] + list(invoice_item_row)))
for parent, description, item_wise_tax_detail, tax_amount in tax_details:
description = handle_html(description)
if description not in tax_columns and tax_amount:
for parent, account_head, item_wise_tax_detail, tax_amount in tax_details:
if account_head not in tax_columns and tax_amount:
# as description is text editor earlier and markup can break the column convention in reports
tax_columns.append(description)
tax_columns.append(account_head)
if item_wise_tax_detail:
try:
@@ -175,17 +177,17 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
for d in item_row_map.get(parent, {}).get(item_code, []):
item_tax_amount = tax_amount
if item_tax_amount:
itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({
itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict({
"tax_amount": flt(item_tax_amount, tax_amount_precision)
})
except ValueError:
continue
tax_columns.sort()
for desc in tax_columns:
for account_head in tax_columns:
columns.append({
"label": desc,
"fieldname": frappe.scrub(desc),
"label": account_head,
"fieldname": frappe.scrub(account_head),
"fieldtype": "Float",
"width": 110
})
@@ -212,3 +214,76 @@ def get_merged_data(columns, data):
return result
@frappe.whitelist()
def get_json(filters, report_name, data):
filters = json.loads(filters)
report_data = json.loads(data)
gstin = filters.get('company_gstin') or get_company_gstin_number(filters["company"])
if not filters.get('from_date') or not filters.get('to_date'):
frappe.throw(_("Please enter From Date and To Date to generate JSON"))
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
gst_json = {"version": "GST2.3.4",
"hash": "hash", "gstin": gstin, "fp": fp}
gst_json["hsn"] = {
"data": get_hsn_wise_json_data(filters, report_data)
}
return {
'report_name': report_name,
'data': gst_json
}
@frappe.whitelist()
def download_json_file():
'''download json content in a file'''
data = frappe._dict(frappe.local.form_dict)
frappe.response['filename'] = frappe.scrub("{0}".format(data['report_name'])) + '.json'
frappe.response['filecontent'] = data['data']
frappe.response['content_type'] = 'application/json'
frappe.response['type'] = 'download'
def get_hsn_wise_json_data(filters, report_data):
filters = frappe._dict(filters)
gst_accounts = get_gst_accounts(filters.company)
data = []
count = 1
for hsn in report_data:
row = {
"num": count,
"hsn_sc": hsn.get("gst_hsn_code"),
"desc": hsn.get("description"),
"uqc": hsn.get("stock_uom").upper(),
"qty": hsn.get("stock_qty"),
"val": flt(hsn.get("total_amount"), 2),
"txval": flt(hsn.get("taxable_amount", 2)),
"iamt": 0.0,
"camt": 0.0,
"samt": 0.0,
"csamt": 0.0
}
for account in gst_accounts.get('igst_account'):
row['iamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
for account in gst_accounts.get('cgst_account'):
row['camt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
for account in gst_accounts.get('sgst_account'):
row['samt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
for account in gst_accounts.get('cess_account'):
row['csamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
data.append(row)
count +=1
return data

View File

@@ -262,9 +262,17 @@ def _make_customer(source_name, ignore_permissions=False):
return customer
else:
raise
except frappe.MandatoryError:
except frappe.MandatoryError as e:
mandatory_fields = e.args[0].split(':')[1].split(',')
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
frappe.local.message_log = []
frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "<br>"
message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
message += _("Please create Customer from Lead {0}.").format(lead_link)
frappe.throw(message, title=_("Mandatory Missing"))
else:
return customer_name
else:

View File

@@ -20,6 +20,13 @@ frappe.ready(() => {
options: 'Email',
reqd: 1
},
{
fieldtype: 'Data',
label: __('Phone Number'),
fieldname: 'phone',
options: 'Phone',
reqd: 1
},
{
fieldtype: 'Data',
label: __('Subject'),