Merge branch 'develop' into crm-carry-forward-communication-comments

This commit is contained in:
Anupam Kumar 2021-11-30 10:11:23 +05:30 committed by GitHub
commit 83e3e58fe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 567 additions and 88 deletions

View File

@ -434,7 +434,7 @@ def get_pi_matching_query(amount_condition):
def get_ec_matching_query(bank_account, company, amount_condition): def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query # get matching Expense Claim query
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])] filters={"default_account": bank_account}, fields=["parent"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )' mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company) company_currency = get_company_currency(company)

View File

@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', {
if (frm.doc.repair_status == "Completed") { if (frm.doc.repair_status == "Completed") {
frm.set_value('completion_date', frappe.datetime.now_datetime()); frm.set_value('completion_date', frappe.datetime.now_datetime());
} }
},
stock_items_on_form_rendered() {
erpnext.setup_serial_or_batch_no();
} }
}); });

View File

@ -118,9 +118,10 @@ class AssetRepair(AccountsController):
for stock_item in self.get('stock_items'): for stock_item in self.get('stock_items'):
stock_entry.append('items', { stock_entry.append('items', {
"s_warehouse": self.warehouse, "s_warehouse": self.warehouse,
"item_code": stock_item.item, "item_code": stock_item.item_code,
"qty": stock_item.consumed_quantity, "qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate "basic_rate": stock_item.valuation_rate,
"serial_no": stock_item.serial_no
}) })
stock_entry.insert() stock_entry.insert()

View File

@ -11,12 +11,15 @@ from erpnext.assets.doctype.asset.test_asset import (
create_asset_data, create_asset_data,
set_depreciation_settings_in_company, set_depreciation_settings_in_company,
) )
from erpnext.stock.doctype.item.test_item import create_item
class TestAssetRepair(unittest.TestCase): class TestAssetRepair(unittest.TestCase):
def setUp(self): @classmethod
def setUpClass(cls):
set_depreciation_settings_in_company() set_depreciation_settings_in_company()
create_asset_data() create_asset_data()
create_item("_Test Stock Item")
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
def test_update_status(self): def test_update_status(self):
@ -70,9 +73,28 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(stock_entry.stock_entry_type, "Material Issue") self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse) self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item) self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
def test_serialized_item_consumption(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
stock_entry = make_serialized_item()
serial_nos = stock_entry.get("items")[0].serial_no
serial_no = serial_nos.split("\n")[0]
# should not raise any error
create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
# should raise error
asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
item_code = stock_entry.get("items")[0].item_code)
asset_repair.repair_status = "Completed"
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
def test_increase_in_asset_value_due_to_stock_consumption(self): def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation = 1, submit=1) asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset) initial_asset_value = get_asset_value(asset)
@ -137,11 +159,12 @@ def create_asset_repair(**args):
if args.stock_consumption: if args.stock_consumption:
asset_repair.stock_consumption = 1 asset_repair.stock_consumption = 1
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
asset_repair.append("stock_items", { asset_repair.append("stock_items", {
"item": args.item or args.item_code or "_Test Item", "item_code": args.item_code or "_Test Stock Item",
"valuation_rate": args.rate if args.get("rate") is not None else 100, "valuation_rate": args.rate if args.get("rate") is not None else 100,
"consumed_quantity": args.qty or 1 "consumed_quantity": args.qty or 1,
"serial_no": args.serial_no
}) })
asset_repair.insert(ignore_if_duplicate=True) asset_repair.insert(ignore_if_duplicate=True)
@ -158,7 +181,7 @@ def create_asset_repair(**args):
}) })
stock_entry.append('items', { stock_entry.append('items', {
"t_warehouse": asset_repair.warehouse, "t_warehouse": asset_repair.warehouse,
"item_code": asset_repair.stock_items[0].item, "item_code": asset_repair.stock_items[0].item_code,
"qty": asset_repair.stock_items[0].consumed_quantity "qty": asset_repair.stock_items[0].consumed_quantity
}) })
stock_entry.submit() stock_entry.submit()

View File

@ -5,19 +5,13 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"item", "item_code",
"valuation_rate", "valuation_rate",
"consumed_quantity", "consumed_quantity",
"total_value" "total_value",
"serial_no"
], ],
"fields": [ "fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{ {
"fetch_from": "item.valuation_rate", "fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate", "fieldname": "valuation_rate",
@ -38,12 +32,24 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Total Value", "label": "Total Value",
"read_only": 1 "read_only": 1
},
{
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-05-12 03:19:55.006300", "modified": "2021-11-11 18:23:00.492483",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Repair Consumed Item", "name": "Asset Repair Consumed Item",

View File

@ -248,20 +248,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

@ -5,6 +5,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate from frappe.utils import flt, nowdate
import erpnext import erpnext
@ -41,24 +42,34 @@ class EmployeeAdvance(Document):
self.status = "Cancelled" self.status = "Cancelled"
def set_total_advance_paid(self): def set_total_advance_paid(self):
paid_amount = frappe.db.sql(""" gle = frappe.qb.DocType("GL Entry")
select ifnull(sum(debit), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and against_voucher = %s
and party_type = 'Employee'
and party = %s
""", (self.name, self.employee), as_dict=1)[0].paid_amount
return_amount = frappe.db.sql(""" paid_amount = (
select ifnull(sum(credit), 0) as return_amount frappe.qb.from_(gle)
from `tabGL Entry` .select(Sum(gle.debit).as_("paid_amount"))
where against_voucher_type = 'Employee Advance' .where(
and voucher_type != 'Expense Claim' (gle.against_voucher_type == 'Employee Advance')
and against_voucher = %s & (gle.against_voucher == self.name)
and party_type = 'Employee' & (gle.party_type == 'Employee')
and party = %s & (gle.party == self.employee)
""", (self.name, self.employee), as_dict=1)[0].return_amount & (gle.docstatus == 1)
& (gle.is_cancelled == 0)
)
).run(as_dict=True)[0].paid_amount or 0
return_amount = (
frappe.qb.from_(gle)
.select(Sum(gle.credit).as_("return_amount"))
.where(
(gle.against_voucher_type == 'Employee Advance')
& (gle.voucher_type != 'Expense Claim')
& (gle.against_voucher == self.name)
& (gle.party_type == 'Employee')
& (gle.party == self.employee)
& (gle.docstatus == 1)
& (gle.is_cancelled == 0)
)
).run(as_dict=True)[0].return_amount or 0
if paid_amount != 0: if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate) paid_amount = flt(paid_amount) / flt(self.exchange_rate)

View File

@ -34,6 +34,24 @@ class TestEmployeeAdvance(unittest.TestCase):
journal_entry1 = make_payment_entry(advance) journal_entry1 = make_payment_entry(advance)
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
def test_paid_amount_on_pe_cancellation(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name)
pe = make_payment_entry(advance)
pe.submit()
advance.reload()
self.assertEqual(advance.paid_amount, 1000)
self.assertEqual(advance.status, "Paid")
pe.cancel()
advance.reload()
self.assertEqual(advance.paid_amount, 0)
self.assertEqual(advance.status, "Unpaid")
def test_repay_unclaimed_amount_from_salary(self): def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance") employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})

View File

@ -94,7 +94,6 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Sanctioned Amount", "label": "Sanctioned Amount",
"no_copy": 1,
"oldfieldname": "sanctioned_amount", "oldfieldname": "sanctioned_amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
@ -120,7 +119,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-18 17:26:09.703215", "modified": "2021-11-26 14:23:45.539922",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim Detail", "name": "Expense Claim Detail",

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

@ -89,7 +89,7 @@ def get_bom_stock(filters):
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
def get_manufacturer_records(): def get_manufacturer_records():
details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
manufacture_details = frappe._dict() manufacture_details = frappe._dict()
for detail in details: for detail in details:
dic = manufacture_details.setdefault(detail.get('parent'), {}) dic = manufacture_details.setdefault(detail.get('parent'), {})

View File

@ -0,0 +1,70 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Work Order Consumed Materials"] = {
"filters": [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
label: __("From Date"),
fieldname:"from_date",
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today(),
reqd: 1
},
{
label: __("Work Order"),
fieldname: "name",
fieldtype: "Link",
options: "Work Order",
get_query: function() {
return {
filters: {
status: ["in", ["In Process", "Completed", "Stopped"]]
}
}
}
},
{
label: __("Production Item"),
fieldname: "production_item",
fieldtype: "Link",
depends_on: "eval: !doc.name",
options: "Item"
},
{
label: __("Status"),
fieldname: "status",
fieldtype: "Select",
options: ["In Process", "Completed", "Stopped"]
},
{
label: __("Excess Materials Consumed"),
fieldname: "show_extra_consumed_materials",
fieldtype: "Check"
}
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname == "raw_material_name" && data && data.extra_consumed_qty > 0 ) {
value = `<div style="color:red">${value}</div>`;
}
return value;
},
};

View File

@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-11-22 17:36:11.886939",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2021-11-22 17:36:14.999091",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Consumed Materials",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Work Order",
"report_name": "Work Order Consumed Materials",
"report_type": "Script Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@ -0,0 +1,131 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_data(report_filters):
fields = get_fields()
filters = get_filter_condition(report_filters)
wo_items = {}
for d in frappe.get_all("Work Order", filters = filters, fields=fields):
d.extra_consumed_qty = 0.0
if d.consumed_qty and d.consumed_qty > d.required_qty:
d.extra_consumed_qty = d.consumed_qty - d.required_qty
if d.extra_consumed_qty or not report_filters.show_extra_consumed_materials:
wo_items.setdefault((d.name, d.production_item), []).append(d)
data = []
for key, wo_data in wo_items.items():
for index, row in enumerate(wo_data):
if index != 0:
#If one work order has multiple raw materials then show parent data in the first row only
for field in ["name", "status", "production_item", "qty", "produced_qty"]:
row[field] = ""
data.append(row)
return data
def get_fields():
return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code",
"`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`",
"`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`",
"`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`",
"`tabWork Order`.`produced_qty`"]
def get_filter_condition(report_filters):
filters = {
"docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]),
"creation": ("between", [report_filters.from_date, report_filters.to_date])
}
for field in ["name", "production_item", "company", "status"]:
value = report_filters.get(field)
if value:
key = f"`{field}`"
filters.update({key: value})
return filters
def get_columns():
return [
{
"label": _("Id"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
"width": 80
},
{
"label": _("Status"),
"fieldname": "status",
"fieldtype": "Data",
"width": 80
},
{
"label": _("Production Item"),
"fieldname": "production_item",
"fieldtype": "Link",
"options": "Item",
"width": 130
},
{
"label": _("Qty to Produce"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 120
},
{
"label": _("Produced Qty"),
"fieldname": "produced_qty",
"fieldtype": "Float",
"width": 110
},
{
"label": _("Raw Material Item"),
"fieldname": "raw_material_item_code",
"fieldtype": "Link",
"options": "Item",
"width": 150
},
{
"label": _("Item Name"),
"fieldname": "raw_material_name",
"width": 130
},
{
"label": _("Required Qty"),
"fieldname": "required_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Transferred Qty"),
"fieldname": "transferred_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Consumed Qty"),
"fieldname": "consumed_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Extra Consumed Qty"),
"fieldname": "extra_consumed_qty",
"fieldtype": "Float",
"width": 100
}
]

View File

@ -1,10 +1,6 @@
{ {
"charts": [ "charts": [],
{ "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports &amp; Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"chart_name": "Produced Quantity"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
"creation": "2020-03-02 17:11:37.032604", "creation": "2020-03-02 17:11:37.032604",
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
@ -140,14 +136,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{ {
"dependencies": "Work Order", "dependencies": "Work Order",
"hidden": 0, "hidden": 0,
@ -295,9 +283,126 @@
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 10,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Production Planning Report",
"link_count": 0,
"link_to": "Production Planning Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Work Order Summary",
"link_count": 0,
"link_to": "Work Order Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Quality Inspection",
"hidden": 0,
"is_query_report": 1,
"label": "Quality Inspection Summary",
"link_count": 0,
"link_to": "Quality Inspection Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Downtime Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Downtime Analysis",
"link_count": 0,
"link_to": "Downtime Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Job Card",
"hidden": 0,
"is_query_report": 1,
"label": "Job Card Summary",
"link_count": 0,
"link_to": "Job Card Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Search",
"link_count": 0,
"link_to": "BOM Search",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Stock Report",
"link_count": 0,
"link_to": "BOM Stock Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Production Analytics",
"link_count": 0,
"link_to": "Production Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Operations Time",
"link_count": 0,
"link_to": "BOM Operations Time",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Work Order Consumed Materials",
"link_count": 0,
"link_to": "Work Order Consumed Materials",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2021-08-05 12:16:00.825742", "modified": "2021-11-22 17:55:03.524496",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing", "name": "Manufacturing",

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

@ -569,17 +569,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

@ -106,14 +106,14 @@ def set_address_details(row, special_characters):
row.update({'ship_to_state': row.to_state}) row.update({'ship_to_state': row.to_state})
def set_taxes(row, filters): def set_taxes(row, filters):
taxes = frappe.get_list("Sales Taxes and Charges", taxes = frappe.get_all("Sales Taxes and Charges",
filters={ filters={
'parent': row.dn_id 'parent': row.dn_id
}, },
fields=('item_wise_tax_detail', 'account_head')) fields=('item_wise_tax_detail', 'account_head'))
account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"] account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"]
taxes_list = frappe.get_list("GST Account", taxes_list = frappe.get_all("GST Account",
filters={ filters={
"parent": "GST Settings", "parent": "GST Settings",
"company": filters.company "company": filters.company

View File

@ -41,7 +41,7 @@ class VATAuditReport(object):
return self.columns, self.data return self.columns, self.data
def get_sa_vat_accounts(self): def get_sa_vat_accounts(self):
self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", self.sa_vat_accounts = frappe.get_all("South Africa VAT Account",
filters = {"parent": self.filters.company}, pluck="account") filters = {"parent": self.filters.company}, pluck="account")
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings") link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")

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

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

@ -222,10 +222,11 @@ class Item(WebsiteGenerator):
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5)) 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
def validate_website_image(self): def validate_website_image(self):
"""Validate if the website image is a public file"""
if frappe.flags.in_import: if frappe.flags.in_import:
return return
"""Validate if the website image is a public file"""
auto_set_website_image = False auto_set_website_image = False
if not self.website_image and self.image: if not self.website_image and self.image:
auto_set_website_image = True auto_set_website_image = True
@ -255,10 +256,11 @@ class Item(WebsiteGenerator):
self.website_image = None self.website_image = None
def make_thumbnail(self): def make_thumbnail(self):
"""Make a thumbnail of `website_image`"""
if frappe.flags.in_import: if frappe.flags.in_import:
return return
"""Make a thumbnail of `website_image`"""
import requests.exceptions import requests.exceptions
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"): if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):

View File

@ -488,7 +488,7 @@ class TestItem(ERPNextTestCase):
item_doc.save() item_doc.save()
# Check values saved correctly # Check values saved correctly
barcodes = frappe.get_list( barcodes = frappe.get_all(
'Item Barcode', 'Item Barcode',
fields=['barcode', 'barcode_type'], fields=['barcode', 'barcode_type'],
filters={'parent': item_code}) filters={'parent': item_code})

View File

@ -299,7 +299,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,
@ -317,6 +317,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,