Merge branch 'develop' into dimension-wise-accounts-balance-reports
This commit is contained in:
commit
2da6ab7f2a
@ -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'''
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
7
erpnext/change_log/v13/v13.0.2.md
Normal file
7
erpnext/change_log/v13/v13.0.2.md
Normal 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))
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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")]):
|
||||
|
@ -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 * * * *": [
|
||||
|
@ -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
|
||||
|
@ -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])
|
24
erpnext/patches/v13_0/make_non_standard_user_type.py
Normal file
24
erpnext/patches/v13_0/make_non_standard_user_type.py
Normal 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()
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user