Merge branch 'develop' into dimension-wise-accounts-balance-reports

This commit is contained in:
Afshan 2021-04-15 18:07:04 +05:30 committed by GitHub
commit 2da6ab7f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 325 additions and 84 deletions

View File

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

View File

@ -175,22 +175,24 @@
},
{
"fieldname": "deposit",
"oldfieldname": "debit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Deposit"
"label": "Deposit",
"oldfieldname": "debit",
"options": "currency"
},
{
"fieldname": "withdrawal",
"oldfieldname": "credit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Withdrawal"
"label": "Withdrawal",
"oldfieldname": "credit",
"options": "currency"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-12-30 19:40:54.221070",
"modified": "2021-04-14 17:31:58.963529",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@ -13,6 +13,8 @@
"po_required",
"pr_required",
"maintain_same_rate",
"maintain_same_rate_action",
"role_to_override_stop_action",
"allow_multiple_items",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
@ -89,6 +91,23 @@
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"default": "Stop",
"depends_on": "maintain_same_rate",
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action",
"fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained",
"mandatory_depends_on": "maintain_same_rate",
"options": "Stop\nWarn"
},
{
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
"fieldname": "role_to_override_stop_action",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"options": "Role"
}
],
"icon": "fa fa-cog",
@ -96,7 +115,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-03-02 17:34:04.190677",
"modified": "2021-04-04 20:01:44.087066",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@ -56,6 +56,8 @@
"base_net_amount",
"warehouse_and_reference",
"warehouse",
"actual_qty",
"company_total_stock",
"material_request",
"material_request_item",
"sales_order",
@ -743,6 +745,22 @@
"options": "currency",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "actual_qty",
"fieldtype": "Float",
"label": "Available Qty at Warehouse",
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "company_total_stock",
"fieldtype": "Float",
"label": "Available Qty at Company",
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "discount_and_margin_section",
@ -791,7 +809,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-02-23 01:00:27.132705",
"modified": "2021-03-22 11:46:12.357435",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -0,0 +1,7 @@
## Version 13.0.2 Release Notes
### Fixes
- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231))
- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306))
- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314))
- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295))

View File

@ -6,6 +6,7 @@ import frappe
from frappe import _, msgprint
from frappe.utils import flt,cint, cstr, getdate
from six import iteritems
from collections import OrderedDict
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
@ -391,10 +392,12 @@ class BuyingController(StockController):
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
for batch_data in batches_qty:
qty = batch_data['qty']
raw_material.batch_no = batch_data['batch']
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
if qty > 0:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
else:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
@ -1056,7 +1059,7 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
for batch_data in transferred_batches:
key = ((batch_data.item_code, fg_item)
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
transferred_batch_qty_map.setdefault(key, {})
transferred_batch_qty_map.setdefault(key, OrderedDict())
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
return transferred_batch_qty_map
@ -1109,8 +1112,14 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty
if available_qty >= required_qty:
available_batches.append({'batch': batch, 'qty': required_qty})
break
else:
elif available_qty != 0:
available_batches.append({'batch': batch, 'qty': available_qty})
required_qty -= available_qty
for row in available_batches:
if backflushed_batches.get(row.get('batch'), 0) > 0:
backflushed_batches[row.get('batch')] += row.get('qty')
else:
backflushed_batches[row.get('batch')] = row.get('qty')
return available_batches

View File

@ -325,7 +325,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
and status not in ("Stopped", "Closed") %(fcond)s
and (
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
or `tabDelivery Note`.grand_total = 0
or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
or (
`tabDelivery Note`.is_return = 1
and return_against in (select name from `tabDelivery Note` where per_billed < 100)

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
from frappe.model.meta import get_field_precision
from erpnext.stock.utils import get_incoming_rate
from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
@ -389,10 +390,24 @@ def make_return_doc(doctype, source_name, target_doc=None):
return doclist
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
item_row=None, voucher_detail_no=None, sle=None):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
if not return_against and voucher_type == 'Sales Invoice' and sle:
return get_incoming_rate({
"item_code": sle.item_code,
"warehouse": sle.warehouse,
"posting_date": sle.get('posting_date'),
"posting_time": sle.get('posting_time'),
"qty": sle.actual_qty,
"serial_no": sle.get('serial_no'),
"company": sle.company,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no
}, raise_error_if_no_rate=False)
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,

View File

@ -311,14 +311,16 @@ class SellingController(StockController):
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if not cint(self.get("is_return")):
if not self.get("return_against"):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.get('posting_date') or self.get('transaction_date'),
"posting_time": self.get('posting_time') or nowtime(),
"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,

View File

@ -291,10 +291,13 @@ class calculate_taxes_and_totals(object):
# set precision in the last item iteration
if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax)
self._set_in_company_currency(tax,
["tax_amount", "tax_amount_after_discount_amount"])
self.round_off_base_values(tax)
self.set_cumulative_total(i, tax)
self._set_in_company_currency(tax,
["total", "tax_amount", "tax_amount_after_discount_amount"])
self._set_in_company_currency(tax, ["total"])
# adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
@ -341,20 +344,11 @@ class calculate_taxes_and_totals(object):
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
if not self.doc.get("is_consolidated"):
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
return current_tax_amount
def get_final_current_tax_amount(self, tax, current_tax_amount):
# Some countries need individual tax components to be rounded
# Handeled via regional doctypess
if tax.account_head in frappe.flags.round_off_applicable_accounts:
current_tax_amount = round(current_tax_amount, 0)
return current_tax_amount
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
# store tax breakup for each item
key = item.item_code or item.item_name
@ -365,10 +359,20 @@ class calculate_taxes_and_totals(object):
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
def round_off_totals(self, tax):
if tax.account_head in frappe.flags.round_off_applicable_accounts:
tax.tax_amount = round(tax.tax_amount, 0)
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
tax.precision("tax_amount"))
def round_off_base_values(self, tax):
# Round off to nearest integer based on regional settings
if tax.account_head in frappe.flags.round_off_applicable_accounts:
tax.base_tax_amount = round(tax.base_tax_amount, 0)
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
def manipulate_grand_total_for_inclusive_tax(self):
# if fully inclusive taxes and diff
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):

View File

@ -307,6 +307,8 @@ auto_cancel_exempted_doctypes= [
"Inpatient Medication Entry"
]
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
scheduler_events = {
"cron": {
"0/30 * * * *": [

View File

@ -771,3 +771,4 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note
erpnext.patches.v12_0.purchase_receipt_status
erpnext.patches.v13_0.fix_non_unique_represents_company
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021

View File

@ -12,5 +12,5 @@ def execute():
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
""", (creds.get('gstin')))
if company_name and len(company_name) == 1:
if company_name and len(company_name) > 0:
frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])

View File

@ -0,0 +1,24 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from six import iteritems
from erpnext.setup.install import add_non_standard_user_types
def execute():
doctype_dict = {
'projects': ['Timesheet'],
'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
}
for module, doctypes in iteritems(doctype_dict):
for doctype in doctypes:
frappe.reload_doc(module, 'doctype', doctype)
frappe.flags.ignore_select_perm = True
frappe.flags.update_select_perm_after_migrate = True
add_non_standard_user_types()

View File

@ -633,8 +633,6 @@ class SalarySlip(TransactionBase):
if additional_salary:
component_row.default_amount = 0
component_row.additional_amount = amount
component_row.additional_salary = additional_salary.name
component_row.deduct_full_tax_on_selected_payroll_date = \
additional_salary.deduct_full_tax_on_selected_payroll_date
else:

View File

@ -216,7 +216,8 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
child: item,
args: {
item_code: item.item_code,
warehouse: item.warehouse
warehouse: item.warehouse,
company: doc.company
}
});
}

View File

@ -323,12 +323,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
// set precision in the last item iteration
if (n == me.frm.doc["items"].length - 1) {
me.round_off_totals(tax);
me.set_in_company_currency(tax,
["tax_amount", "tax_amount_after_discount_amount"]);
me.round_off_base_values(tax);
// in tax.total, accumulate grand total for each item
me.set_cumulative_total(i, tax);
me.set_in_company_currency(tax,
["total", "tax_amount", "tax_amount_after_discount_amount"]);
me.set_in_company_currency(tax, ["total"]);
// adjust Discount Amount loss in last tax iteration
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
@ -393,20 +396,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
current_tax_amount = tax_rate * item.qty;
}
current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount);
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
return current_tax_amount;
},
get_final_tax_amount: function(tax, current_tax_amount) {
if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
current_tax_amount = Math.round(current_tax_amount);
}
return current_tax_amount;
},
set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
// store tax breakup for each item
let tax_detail = tax.item_wise_tax_detail;
@ -420,10 +414,22 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
},
round_off_totals: function(tax) {
if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
tax.tax_amount= Math.round(tax.tax_amount);
tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount);
}
tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax));
},
round_off_base_values: function(tax) {
if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
tax.base_tax_amount= Math.round(tax.base_tax_amount);
tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount);
}
},
manipulate_grand_total_for_inclusive_tax: function() {
var me = this;
// if fully inclusive taxes and diff

View File

@ -291,17 +291,15 @@ $.extend(erpnext.utils, {
return options[0];
}
},
copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
var d = locals[dt][dn];
if(d[fieldname]){
var cl = doc[table_fieldname] || [];
for(var i = 0; i < cl.length; i++) {
overrides_parent_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
if (doc[parent_fieldname]) {
let cl = doc[table_fieldname] || [];
for (let i = 0; i < cl.length; i++) {
cl[i][fieldname] = doc[parent_fieldname];
}
frappe.refresh_field(table_fieldname);
}
refresh_field(table_fieldname);
},
create_new_doc: function (doctype, update_fields) {
frappe.model.with_doctype(doctype, function() {
var new_doc = frappe.model.get_new_doc(doctype);

View File

@ -353,9 +353,9 @@ erpnext.SerialNoBatchSelector = Class.extend({
return row.on_grid_fields_dict.batch_no.get_value();
}
});
if (selected_batches.includes(val)) {
if (selected_batches.includes(batch_no)) {
this.set_value("");
frappe.throw(__('Batch {0} already selected.', [val]));
frappe.throw(__('Batch {0} already selected.', [batch_no]));
}
if (me.warehouse_details.name) {

View File

@ -466,21 +466,24 @@ def make_einvoice(invoice):
try:
einvoice = safe_json_load(einvoice)
einvoice = santize_einvoice_fields(einvoice)
validate_totals(einvoice)
except Exception:
log_error(einvoice)
link_to_error_list = '<a href="List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
frappe.throw(
_('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
invoice.name, link_to_error_list),
title=_('E Invoice Creation Failed')
)
show_link_to_error_log(invoice, einvoice)
validate_totals(einvoice)
return einvoice
def show_link_to_error_log(invoice, einvoice):
err_log = log_error(einvoice)
link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
frappe.throw(
_('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
invoice.name, link_to_error_log),
title=_('E Invoice Creation Failed')
)
def log_error(data=None):
if not isinstance(data, dict):
if isinstance(data, six.string_types):
data = json.loads(data)
seperator = "--" * 50
@ -587,7 +590,7 @@ class GSPConnector():
self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
def get_seller_gstin(self):
gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
if not gstin:
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
return gstin

View File

@ -38,11 +38,19 @@ class Customer(TransactionBase):
set_name_by_naming_series(self)
def get_customer_name(self):
if frappe.db.get_value("Customer", self.customer_name):
if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
where name like %s""", "%{0} - %".format(self.customer_name), as_list=1)[0][0]
count = cint(count) + 1
return "{0} - {1}".format(self.customer_name, cstr(count))
new_customer_name = "{0} - {1}".format(self.customer_name, cstr(count))
msgprint(_("Changed customer name to '{}' as '{}' already exists.")
.format(new_customer_name, self.customer_name),
title=_("Note"), indicator="yellow")
return new_customer_name
return self.customer_name

View File

@ -18,6 +18,8 @@
"dn_required",
"sales_update_frequency",
"maintain_same_sales_rate",
"maintain_same_rate_action",
"role_to_override_stop_action",
"editable_price_list_rate",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
@ -133,6 +135,23 @@
"fieldname": "hide_tax_id",
"fieldtype": "Check",
"label": "Hide Customer's Tax ID from Sales Transactions"
},
{
"default": "Stop",
"depends_on": "maintain_same_sales_rate",
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action",
"fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained",
"mandatory_depends_on": "maintain_same_sales_rate",
"options": "Stop\nWarn"
},
{
"depends_on": "eval: doc.maintain_same_rate_action == 'Stop'",
"fieldname": "role_to_override_stop_action",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"options": "Role"
}
],
"icon": "fa fa-cog",
@ -140,7 +159,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-03-02 17:35:53.603607",
"modified": "2021-04-04 20:18:12.814624",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@ -204,11 +204,11 @@ erpnext.PointOfSale.PastOrderSummary = class {
print_receipt() {
const frm = this.events.get_frm();
frappe.utils.print(
frm.doctype,
frm.docname,
this.doc.doctype,
this.doc.name,
frm.pos_print_format,
frm.doc.letter_head,
frm.doc.language || frappe.boot.lang
this.doc.letter_head,
this.doc.language || frappe.boot.lang
);
}

View File

@ -15,7 +15,7 @@ def delete_company_transactions(company_name):
frappe.only_for("System Manager")
doc = frappe.get_doc("Company", company_name)
if frappe.session.user != doc.owner:
if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
frappe.PermissionError)

View File

@ -8,9 +8,11 @@ from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import D
from .default_success_action import get_default_success_action
from frappe import _
from frappe.utils import cint
from frappe.installer import update_site_config
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
from six import iteritems
default_mail_footer = """<div style="padding: 7px; text-align: right; color: #888"><small>Sent via
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
@ -29,6 +31,7 @@ def after_install():
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
add_non_standard_user_types()
frappe.db.commit()
@ -164,3 +167,81 @@ def add_standard_navbar_items():
def add_app_name():
frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
def add_non_standard_user_types():
user_types = get_user_types_data()
user_type_limit = {}
for user_type, data in iteritems(user_types):
user_type_limit.setdefault(frappe.scrub(user_type), 10)
update_site_config('user_type_doctype_limit', user_type_limit)
for user_type, data in iteritems(user_types):
create_custom_role(data)
create_user_type(user_type, data)
def get_user_types_data():
return {
'Employee Self Service': {
'role': 'Employee Self Service',
'apply_user_permission_on': 'Employee',
'user_id_field': 'user_id',
'doctypes': {
'Salary Slip': ['read'],
'Employee': ['read', 'write'],
'Expense Claim': ['read', 'write', 'create', 'delete'],
'Leave Application': ['read', 'write', 'create', 'delete'],
'Attendance Request': ['read', 'write', 'create', 'delete'],
'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
}
}
}
def create_custom_role(data):
if data.get('role') and not frappe.db.exists('Role', data.get('role')):
frappe.get_doc({
'doctype': 'Role',
'role_name': data.get('role'),
'desk_access': 1,
'is_custom': 1
}).insert(ignore_permissions=True)
def create_user_type(user_type, data):
if frappe.db.exists('User Type', user_type):
doc = frappe.get_cached_doc('User Type', user_type)
doc.user_doctypes = []
else:
doc = frappe.new_doc('User Type')
doc.update({
'name': user_type,
'role': data.get('role'),
'user_id_field': data.get('user_id_field'),
'apply_user_permission_on': data.get('apply_user_permission_on')
})
create_role_permissions_for_doctype(doc, data)
doc.save(ignore_permissions=True)
def create_role_permissions_for_doctype(doc, data):
for doctype, perms in iteritems(data.get('doctypes')):
args = {'document_type': doctype}
for perm in perms:
args[perm] = 1
doc.append('user_doctypes', args)
def update_select_perm_after_install():
if not frappe.flags.update_select_perm_after_migrate:
return
frappe.flags.ignore_select_perm = False
for row in frappe.get_all('User Type', filters= {'is_standard': 0}):
print('Updating user type :- ', row.name)
doc = frappe.get_doc('User Type', row.name)
doc.save()
frappe.flags.update_select_perm_after_migrate = False

View File

@ -922,10 +922,19 @@ def get_projected_qty(item_code, warehouse):
{"item_code": item_code, "warehouse": warehouse}, "projected_qty")}
@frappe.whitelist()
def get_bin_details(item_code, warehouse):
return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
def get_bin_details(item_code, warehouse, company=None):
bin_details = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["projected_qty", "actual_qty", "reserved_qty"], as_dict=True, cache=True) \
or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
if company:
bin_details['company_total_stock'] = get_company_total_stock(item_code, company)
return bin_details
def get_company_total_stock(item_code, company):
return frappe.db.sql("""SELECT sum(actual_qty) from
(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'"""
.format(company, item_code))[0][0]
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):

View File

@ -372,7 +372,8 @@ class update_entries_after(object):
elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"):
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top
rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no)
rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code,
voucher_detail_no=sle.voucher_detail_no, sle = sle)
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"
@ -603,7 +604,7 @@ class update_entries_after(object):
batch = self.wh_data.stock_queue[index]
if qty_to_pop >= batch[0]:
# consume current batch
qty_to_pop = qty_to_pop - batch[0]
qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0])
self.wh_data.stock_queue.pop(index)
if not self.wh_data.stock_queue and qty_to_pop:
# stock finished, qty still remains to be withdrawn
@ -617,8 +618,8 @@ class update_entries_after(object):
batch[0] = batch[0] - qty_to_pop
qty_to_pop = 0
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))
stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue))
stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)))
stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.wh_data.stock_queue)))
if stock_qty:
self.wh_data.valuation_rate = stock_value / flt(stock_qty)
@ -857,3 +858,12 @@ def get_future_sle_with_negative_qty(args):
order by timestamp(posting_date, posting_time) asc
limit 1
""", args, as_dict=1)
def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
""" Rounds off the number to zero only if number is close to zero for decimal
specified in precision. Precision defaults to 6.
"""
if flt(number) < (1.0 / (10**precision)):
return 0
return flt(number)

View File

@ -120,11 +120,11 @@ class TransactionBase(StatusUpdater):
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
if self.doctype in buying_doctypes:
to_disable = "Maintain same rate throughout Purchase cycle"
settings_page = "Buying Settings"
action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
settings_doc = "Buying Settings"
else:
to_disable = "Maintain same rate throughout Sales cycle"
settings_page = "Selling Settings"
action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
settings_doc = "Selling Settings"
for ref_dt, ref_dn_field, ref_link_field in ref_details:
for d in self.get("items"):
@ -132,11 +132,16 @@ class TransactionBase(StatusUpdater):
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
.format(frappe.bold(_(to_disable)),
get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
if action == "Stop":
role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action')
if role_allowed_to_override not in frappe.get_roles():
frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
else:
frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange")
def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):