Merge branch 'version-13-hotfix' into accounts_receivable_multi_currency

This commit is contained in:
Deepesh Garg
2021-11-30 18:20:08 +05:30
committed by GitHub
15 changed files with 160 additions and 93 deletions

View File

@@ -256,20 +256,18 @@ doc_events = {
"validate": "erpnext.regional.india.utils.validate_tax_category" "validate": "erpnext.regional.india.utils.validate_tax_category"
}, },
"Sales Invoice": { "Sales Invoice": {
"after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code",
"on_submit": [ "on_submit": [
"erpnext.regional.create_transaction_log", "erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit", "erpnext.regional.italy.utils.sales_invoice_on_submit",
"erpnext.regional.saudi_arabia.utils.create_qr_code",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction" "erpnext.erpnext_integrations.taxjar_integration.create_transaction"
], ],
"on_cancel": [ "on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" "erpnext.erpnext_integrations.taxjar_integration.delete_transaction",
],
"on_trash": [
"erpnext.regional.check_deletion_permission",
"erpnext.regional.saudi_arabia.utils.delete_qr_code_file" "erpnext.regional.saudi_arabia.utils.delete_qr_code_file"
], ],
"on_trash": "erpnext.regional.check_deletion_permission",
"validate": [ "validate": [
"erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values" "erpnext.regional.india.utils.update_taxable_values"

View File

@@ -96,15 +96,8 @@ class Employee(NestedSet):
'user': self.user_id 'user': self.user_id
}) })
if employee_user_permission_exists: return if employee_user_permission_exists:
return
employee_user_permission_exists = frappe.db.exists('User Permission', {
'allow': 'Employee',
'for_value': self.name,
'user': self.user_id
})
if employee_user_permission_exists: return
add_user_permission("Employee", self.name, self.user_id) add_user_permission("Employee", self.name, self.user_id)
set_user_permission_if_allowed("Company", self.company, self.user_id) set_user_permission_if_allowed("Company", self.company, self.user_id)

View File

@@ -2,7 +2,6 @@
# See license.txt # See license.txt
import unittest import unittest
from datetime import date
import frappe import frappe
from frappe.utils import add_days, getdate from frappe.utils import add_days, getdate
@@ -12,16 +11,14 @@ from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeTransfer(unittest.TestCase): class TestEmployeeTransfer(unittest.TestCase):
def setUp(self): def setUp(self):
make_employee("employee2@transfers.com")
make_employee("employee3@transfers.com")
create_company() create_company()
create_employee()
create_employee_transfer()
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def test_submit_before_transfer_date(self): def test_submit_before_transfer_date(self):
make_employee("employee2@transfers.com")
transfer_obj = frappe.get_doc({ transfer_obj = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"), "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
@@ -43,6 +40,8 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(transfer.docstatus, 1) self.assertEqual(transfer.docstatus, 1)
def test_new_employee_creation(self): def test_new_employee_creation(self):
make_employee("employee3@transfers.com")
transfer = frappe.get_doc({ transfer = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"), "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"),
@@ -63,60 +62,51 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left")
def test_employee_history(self): def test_employee_history(self):
name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name") employee = make_employee("employee4@transfers.com",
doc = frappe.get_doc("Employee",name) company="Test Company",
date_of_birth=getdate("30-09-1980"),
date_of_joining=getdate("01-10-2021"),
department="Accounts - TC",
designation="Accountant"
)
transfer = create_employee_transfer(employee)
count = 0 count = 0
department = ["Accounts - TC", "Management - TC"] department = ["Accounts - TC", "Management - TC"]
designation = ["Accountant", "Manager"] designation = ["Accountant", "Manager"]
dt = [getdate("01-10-2021"), date.today()] dt = [getdate("01-10-2021"), getdate()]
for data in doc.internal_work_history: employee = frappe.get_doc("Employee", employee)
for data in employee.internal_work_history:
self.assertEqual(data.department, department[count]) self.assertEqual(data.department, department[count])
self.assertEqual(data.designation, designation[count]) self.assertEqual(data.designation, designation[count])
self.assertEqual(data.from_date, dt[count]) self.assertEqual(data.from_date, dt[count])
count = count + 1 count = count + 1
data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"]) transfer.cancel()
doc = frappe.get_doc("Employee Transfer", data[0]["name"]) employee.reload()
doc.cancel()
employee_doc = frappe.get_doc("Employee",name)
for data in employee_doc.internal_work_history: for data in employee.internal_work_history:
self.assertEqual(data.designation, designation[0]) self.assertEqual(data.designation, designation[0])
self.assertEqual(data.department, department[0]) self.assertEqual(data.department, department[0])
self.assertEqual(data.from_date, dt[0]) self.assertEqual(data.from_date, dt[0])
def create_employee():
doc = frappe.get_doc({
"doctype": "Employee",
"first_name": "John",
"company": "Test Company",
"gender": "Male",
"date_of_birth": getdate("30-09-1980"),
"date_of_joining": getdate("01-10-2021"),
"department": "Accounts - TC",
"designation": "Accountant"
})
doc.save()
def create_company(): def create_company():
exists = frappe.db.exists("Company", "Test Company") if not frappe.db.exists("Company", "Test Company"):
if not exists: frappe.get_doc({
doc = frappe.get_doc({ "doctype": "Company",
"doctype": "Company", "company_name": "Test Company",
"company_name": "Test Company", "default_currency": "INR",
"default_currency": "INR", "country": "India"
"country": "India" }).insert()
})
doc.save()
def create_employee_transfer(): def create_employee_transfer(employee):
doc = frappe.get_doc({ doc = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"), "employee": employee,
"transfer_date": date.today(), "transfer_date": getdate(),
"transfer_details": [ "transfer_details": [
{ {
"property": "Designation", "property": "Designation",
@@ -135,3 +125,5 @@ def create_employee_transfer():
doc.save() doc.save()
doc.submit() doc.submit()
return doc

View File

@@ -19,8 +19,8 @@ class ShiftAssignment(Document):
validate_active_employee(self.employee) validate_active_employee(self.employee)
self.validate_overlapping_dates() self.validate_overlapping_dates()
if self.end_date and self.end_date <= self.start_date: if self.end_date:
frappe.throw(_("End Date must not be lesser than Start Date")) self.validate_from_to_dates('start_date', 'end_date')
def validate_overlapping_dates(self): def validate_overlapping_dates(self):
if not self.name: if not self.name:

View File

@@ -178,8 +178,9 @@
}, },
{ {
"fieldname": "batch_size", "fieldname": "batch_size",
"fieldtype": "Int", "fieldtype": "Float",
"label": "Batch Size" "label": "Batch Size",
"read_only": 1
}, },
{ {
"fieldname": "sequence_id", "fieldname": "sequence_id",
@@ -200,7 +201,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-24 04:52:54.295168", "modified": "2021-11-29 16:37:18.824489",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order Operation", "name": "Work Order Operation",

View File

@@ -2,7 +2,7 @@
import unittest import unittest
import frappe import frappe
from frappe.utils import add_days, getdate, nowdate from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.projects.doctype.timesheet.test_timesheet import ( from erpnext.projects.doctype.timesheet.test_timesheet import (
@@ -14,21 +14,26 @@ from erpnext.projects.report.project_profitability.project_profitability import
class TestProjectProfitability(unittest.TestCase): class TestProjectProfitability(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql('delete from `tabTimesheet`')
emp = make_employee('test_employee_9@salary.com', company='_Test Company') emp = make_employee('test_employee_9@salary.com', company='_Test Company')
if not frappe.db.exists('Salary Component', 'Timesheet Component'): if not frappe.db.exists('Salary Component', 'Timesheet Component'):
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
make_salary_structure_for_timesheet(emp, company='_Test Company') make_salary_structure_for_timesheet(emp, company='_Test Company')
self.timesheet = make_timesheet(emp, simulate = True, is_billable=1) date = getdate()
self.timesheet = make_timesheet(emp, is_billable=1)
self.salary_slip = make_salary_slip(self.timesheet.name) self.salary_slip = make_salary_slip(self.timesheet.name)
holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
holidays = self.salary_slip.get_holidays_for_employee(date, date)
if holidays: if holidays:
frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1) frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
self.salary_slip.submit() self.salary_slip.submit()
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
self.sales_invoice.due_date = nowdate() self.sales_invoice.due_date = date
self.sales_invoice.submit() self.sales_invoice.submit()
frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
@@ -64,6 +69,4 @@ class TestProjectProfitability(unittest.TestCase):
self.assertEqual(fractional_cost, row.fractional_cost) self.assertEqual(fractional_cost, row.fractional_cost)
def tearDown(self): def tearDown(self):
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() frappe.db.rollback()
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
frappe.get_doc("Timesheet", self.timesheet.name).cancel()

View File

@@ -495,6 +495,11 @@
font-size: var(--text-md); font-size: var(--text-md);
} }
> .item-qty-total-container {
@extend .net-total-container;
padding: 5px 0px 0px 0px;
}
> .taxes-container { > .taxes-container {
display: none; display: none;
flex-direction: column; flex-direction: column;

View File

@@ -571,17 +571,17 @@ def get_item_list(data, doc, hsn_wise=False):
} }
item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol']
hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise)
for hsn_code, taxable_amount in hsn_taxable_amount.items(): for item_or_hsn, taxable_amount in hsn_taxable_amount.items():
item_data = frappe._dict() item_data = frappe._dict()
if not hsn_code: if not item_or_hsn:
frappe.throw(_('GST HSN Code does not exist for one or more items')) frappe.throw(_('GST HSN Code does not exist for one or more items'))
item_data.hsnCode = int(hsn_code) item_data.hsnCode = int(item_or_hsn) if hsn_wise else item_or_hsn
item_data.taxableAmount = taxable_amount item_data.taxableAmount = taxable_amount
item_data.qtyUnit = "" item_data.qtyUnit = ""
for attr in item_data_attrs: for attr in item_data_attrs:
item_data[attr] = 0 item_data[attr] = 0
for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items(): for account, tax_detail in hsn_wise_charges.get(item_or_hsn, {}).items():
account_type = gst_accounts.get(account, '') account_type = gst_accounts.get(account, '')
for tax_acc, attrs in tax_map.items(): for tax_acc, attrs in tax_map.items():
if account_type == tax_acc: if account_type == tax_acc:

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,10 @@
import io import io
import os import os
from base64 import b64encode
import frappe import frappe
from frappe import _
from frappe.utils.data import add_to_date, get_time, getdate
from pyqrcode import create as qr_create from pyqrcode import create as qr_create
from erpnext import get_region from erpnext import get_region
@@ -28,24 +31,74 @@ def create_qr_code(doc, method):
for field in meta.get_image_fields(): for field in meta.get_image_fields():
if field.fieldname == 'qr_code': if field.fieldname == 'qr_code':
from urllib.parse import urlencode ''' TLV conversion for
1. Seller's Name
2. VAT Number
3. Time Stamp
4. Invoice Amount
5. VAT Amount
'''
tlv_array = []
# Sellers Name
# Creating public url to print format seller_name = frappe.db.get_value(
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") 'Company',
doc.company,
'company_name_in_arabic')
# System Language if not seller_name:
language = frappe.get_system_settings('language') frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
params = urlencode({ tag = bytes([1]).hex()
'format': default_print_format or 'Standard', length = bytes([len(seller_name.encode('utf-8'))]).hex()
'_lang': language, value = seller_name.encode('utf-8').hex()
'key': doc.get_signature() tlv_array.append(''.join([tag, length, value]))
})
# VAT Number
tax_id = frappe.db.get_value('Company', doc.company, 'tax_id')
if not tax_id:
frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company))
tag = bytes([2]).hex()
length = bytes([len(tax_id)]).hex()
value = tax_id.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Time Stamp
posting_date = getdate(doc.posting_date)
time = get_time(doc.posting_time)
seconds = time.hour * 60 * 60 + time.minute * 60 + time.second
time_stamp = add_to_date(posting_date, seconds=seconds)
time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ')
tag = bytes([3]).hex()
length = bytes([len(time_stamp)]).hex()
value = time_stamp.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Invoice Amount
invoice_amount = str(doc.total)
tag = bytes([4]).hex()
length = bytes([len(invoice_amount)]).hex()
value = invoice_amount.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# VAT Amount
vat_amount = str(doc.total_taxes_and_charges)
tag = bytes([5]).hex()
length = bytes([len(vat_amount)]).hex()
value = vat_amount.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Joining bytes into one
tlv_buff = ''.join(tlv_array)
# base64 conversion for QR Code
base64_string = b64encode(bytes.fromhex(tlv_buff)).decode()
# creating qr code for the url
url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
qr_image = io.BytesIO() qr_image = io.BytesIO()
url = qr_create(url, error='L') url = qr_create(base64_string, error='L')
url.png(qr_image, scale=2, quiet_zone=1) url.png(qr_image, scale=2, quiet_zone=1)
# making file # making file

View File

@@ -952,8 +952,7 @@
"idx": 82, "idx": 82,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"max_attachments": 1, "modified": "2021-11-30 01:33:21.106073",
"modified": "2021-08-27 20:10:07.864951",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -100,6 +100,10 @@ erpnext.PointOfSale.ItemCart = class {
`<div class="add-discount-wrapper"> `<div class="add-discount-wrapper">
${this.get_discount_icon()} ${__('Add Discount')} ${this.get_discount_icon()} ${__('Add Discount')}
</div> </div>
<div class="item-qty-total-container">
<div class="item-qty-total-label">${__('Total Items')}</div>
<div class="item-qty-total-value">0.00</div>
</div>
<div class="net-total-container"> <div class="net-total-container">
<div class="net-total-label">${__("Net Total")}</div> <div class="net-total-label">${__("Net Total")}</div>
<div class="net-total-value">0.00</div> <div class="net-total-value">0.00</div>
@@ -142,6 +146,7 @@ erpnext.PointOfSale.ItemCart = class {
this.$numpad_section.prepend( this.$numpad_section.prepend(
`<div class="numpad-totals"> `<div class="numpad-totals">
<span class="numpad-item-qty-total"></span>
<span class="numpad-net-total"></span> <span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span> <span class="numpad-grand-total"></span>
</div>` </div>`
@@ -470,6 +475,7 @@ erpnext.PointOfSale.ItemCart = class {
if (!frm) frm = this.events.get_frm(); if (!frm) frm = this.events.get_frm();
this.render_net_total(frm.doc.net_total); this.render_net_total(frm.doc.net_total);
this.render_total_item_qty(frm.doc.items);
const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total;
this.render_grand_total(grand_total); this.render_grand_total(grand_total);
@@ -487,6 +493,21 @@ erpnext.PointOfSale.ItemCart = class {
); );
} }
render_total_item_qty(items) {
var total_item_qty = 0;
items.map((item) => {
total_item_qty = total_item_qty + item.qty;
});
this.$totals_section.find('.item-qty-total-container').html(
`<div>${__('Total Quantity')}</div><div>${total_item_qty}</div>`
);
this.$numpad_section.find('.numpad-item-qty-total').html(
`<div>${__('Total Quantity')}: <span>${total_item_qty}</span></div>`
);
}
render_grand_total(value) { render_grand_total(value) {
const currency = this.events.get_frm().doc.currency; const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total-container').html( this.$totals_section.find('.grand-total-container').html(

View File

@@ -949,8 +949,7 @@
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 1, "modified": "2021-11-30 01:33:06.572442",
"modified": "2021-09-10 12:23:07.277077",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-03-28 10:35:31", "creation": "2013-03-28 10:35:31",
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
@@ -153,11 +154,12 @@
"icon": "fa fa-upload-alt", "icon": "fa fa-upload-alt",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"max_attachments": 1, "links": [],
"modified": "2020-04-08 17:02:47.196206", "modified": "2021-11-30 01:33:51.437194",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reconciliation", "name": "Stock Reconciliation",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -300,7 +300,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"warehouse": warehouse, "warehouse": warehouse,
"income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
"expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
"discount_account": None or get_default_discount_account(args, item_defaults), "discount_account": get_default_discount_account(args, item_defaults),
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no, 'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no, 'has_batch_no': item.has_batch_no,
@@ -318,6 +318,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"net_rate": 0.0, "net_rate": 0.0,
"net_amount": 0.0, "net_amount": 0.0,
"discount_percentage": 0.0, "discount_percentage": 0.0,
"discount_amount": 0.0,
"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,