Merge branch 'develop' into serialized-item-consumption
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 5cbf00b..e7371fb 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -434,7 +434,7 @@
def get_ec_matching_query(bank_account, company, amount_condition):
# 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"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7aeb872..c1b056b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -339,7 +339,7 @@
for k, v in no_oustanding_refs.items():
frappe.msgprint(
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
- .format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
+ .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
@@ -611,7 +611,7 @@
if not total_negative_outstanding:
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
- .format(self.payment_type, ("to" if self.party_type=="Customer" else "from"),
+ .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
self.party_type), InvalidPaymentEntry)
elif paid_amount - additional_charges > total_negative_outstanding:
@@ -1092,7 +1092,7 @@
if not data:
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
- .format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
+ .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
return data
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 516133a..48b5cb9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -728,7 +728,7 @@
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
- "remarks": self.remarks or "Accounting Entry for Stock",
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
"project": item.project or self.project
}, item=item)
@@ -936,7 +936,7 @@
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": valuation_tax[tax.name],
- "remarks": self.remarks or "Accounting Entry for Stock"
+ "remarks": self.remarks or _("Accounting Entry for Stock")
}, item=tax))
@property
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index a1f3ee4..4538675 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -978,7 +978,7 @@
}
if (frm.doc.is_debit_note) {
- frm.set_df_property('return_against', 'label', 'Adjustment Against');
+ frm.set_df_property('return_against', 'label', __('Adjustment Against'));
}
if (frappe.boot.active_domains.includes("Healthcare")) {
@@ -988,10 +988,10 @@
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
frm.add_custom_button(__('Healthcare Services'), function() {
get_healthcare_services_to_invoice(frm);
- },"Get Items From");
+ },__("Get Items From"));
frm.add_custom_button(__('Prescriptions'), function() {
get_drugs_to_invoice(frm);
- },"Get Items From");
+ },__("Get Items From"));
}
}
else {
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index ae17094..7073e32 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -134,7 +134,7 @@
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
- "remarks": self.get("remarks") or "Accounting Entry for Stock",
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
@@ -143,7 +143,7 @@
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
- "remarks": self.get("remarks") or "Accounting Entry for Stock",
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e8aac1d..05c46c5 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -248,20 +248,18 @@
"validate": "erpnext.regional.india.utils.validate_tax_category"
},
"Sales Invoice": {
- "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code",
"on_submit": [
"erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit",
+ "erpnext.regional.saudi_arabia.utils.create_qr_code",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction"
],
"on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
- "erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
- ],
- "on_trash": [
- "erpnext.regional.check_deletion_permission",
+ "erpnext.erpnext_integrations.taxjar_integration.delete_transaction",
"erpnext.regional.saudi_arabia.utils.delete_qr_code_file"
],
+ "on_trash": "erpnext.regional.check_deletion_permission",
"validate": [
"erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values"
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 8a8e8db..7aac2b6 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
import erpnext
@@ -41,24 +42,34 @@
self.status = "Cancelled"
def set_total_advance_paid(self):
- paid_amount = frappe.db.sql("""
- 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
+ gle = frappe.qb.DocType("GL Entry")
- return_amount = frappe.db.sql("""
- select ifnull(sum(credit), 0) as return_amount
- from `tabGL Entry`
- where against_voucher_type = 'Employee Advance'
- and voucher_type != 'Expense Claim'
- and against_voucher = %s
- and party_type = 'Employee'
- and party = %s
- """, (self.name, self.employee), as_dict=1)[0].return_amount
+ paid_amount = (
+ frappe.qb.from_(gle)
+ .select(Sum(gle.debit).as_("paid_amount"))
+ .where(
+ (gle.against_voucher_type == 'Employee Advance')
+ & (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].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:
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 4ecfa60..5f2e720 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -34,6 +34,24 @@
journal_entry1 = make_payment_entry(advance)
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):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index 70a48f9..6edbcb5c 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -94,7 +94,6 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Sanctioned Amount",
- "no_copy": 1,
"oldfieldname": "sanctioned_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -120,7 +119,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-11-26 14:23:45.539922",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
index 647c14b..4e1a464 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -178,8 +178,9 @@
},
{
"fieldname": "batch_size",
- "fieldtype": "Int",
- "label": "Batch Size"
+ "fieldtype": "Float",
+ "label": "Batch Size",
+ "read_only": 1
},
{
"fieldname": "sequence_id",
@@ -200,7 +201,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-11-24 04:52:54.295168",
+ "modified": "2021-11-29 16:37:18.824489",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Operation",
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index cf19cbf..090a3e7 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -89,7 +89,7 @@
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
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()
for detail in details:
dic = manufacture_details.setdefault(detail.get('parent'), {})
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js
new file mode 100644
index 0000000..b2428e8
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js
@@ -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;
+ },
+};
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json
new file mode 100644
index 0000000..2fc986a
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
new file mode 100644
index 0000000..0528348
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
@@ -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
+ }
+ ]
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index cfa80f8..65b4d02 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,10 +1,6 @@
{
- "charts": [
- {
- "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}}]",
+ "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 & 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}}]",
"creation": "2020-03-02 17:11:37.032604",
"docstatus": 0,
"doctype": "Workspace",
@@ -141,14 +137,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Reports",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
@@ -295,9 +283,126 @@
"link_type": "DocType",
"onboard": 0,
"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",
"module": "Manufacturing",
"name": "Manufacturing",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e475229..897e70c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -287,7 +287,7 @@
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
erpnext.patches.v13_0.validate_options_for_data_field
-erpnext.patches.v13_0.create_gst_payment_entry_fields
+erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
index 7e6d67c..4166945 100644
--- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
+++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
@@ -9,24 +9,29 @@
frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges')
frappe.reload_doc('accounts', 'doctype', 'payment_entry')
- custom_fields = {
- 'Payment Entry': [
- dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
- print_hide=1, collapsible=1),
- dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
- print_hide=1, options='Address'),
- dict(fieldname='company_gstin', label='Company GSTIN',
- fieldtype='Data', insert_after='company_address',
- fetch_from='company_address.gstin', print_hide=1, read_only=1),
- dict(fieldname='place_of_supply', label='Place of Supply',
- fieldtype='Data', insert_after='company_gstin',
- print_hide=1, read_only=1),
- dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
- print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
- dict(fieldname='customer_gstin', label='Customer GSTIN',
- fieldtype='Data', insert_after='customer_address',
- fetch_from='customer_address.gstin', print_hide=1, read_only=1)
- ]
- }
+ if frappe.db.exists('Company', {'country': 'India'}):
+ custom_fields = {
+ 'Payment Entry': [
+ dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
+ print_hide=1, collapsible=1),
+ dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
+ print_hide=1, options='Address'),
+ dict(fieldname='company_gstin', label='Company GSTIN',
+ fieldtype='Data', insert_after='company_address',
+ fetch_from='company_address.gstin', print_hide=1, read_only=1),
+ dict(fieldname='place_of_supply', label='Place of Supply',
+ fieldtype='Data', insert_after='company_gstin',
+ print_hide=1, read_only=1),
+ dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
+ print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
+ dict(fieldname='customer_gstin', label='Customer GSTIN',
+ fieldtype='Data', insert_after='customer_address',
+ fetch_from='customer_address.gstin', print_hide=1, read_only=1)
+ ]
+ }
- create_custom_fields(custom_fields, update=True)
\ No newline at end of file
+ create_custom_fields(custom_fields, update=True)
+ else:
+ fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin']
+ for field in fields:
+ frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
\ No newline at end of file
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 4bd9195..9746fde 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -569,17 +569,17 @@
}
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)
- 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()
- if not hsn_code:
+ if not item_or_hsn:
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.qtyUnit = ""
for attr in item_data_attrs:
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, '')
for tax_acc, attrs in tax_map.items():
if account_type == tax_acc:
diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
index 681f72f..8e9a728 100644
--- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
+++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
@@ -10,14 +10,14 @@
"docstatus": 0,
"doctype": "Print Format",
"font_size": 14,
- "html": "<div class=\"ksa-vat-format\">\n <div class=\"qr-flex\">\n <div style=\"qr-flex: 1\">\n <h2 class=\"invoice-heading\">TAX INVOICE</h2>\n <h2 class=\"invoice-heading\">\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629</h2>\n </div>\n \n <img class=\"qr-code\" src={{doc.qr_code}}>\n </div>\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n <table class=\"ksa-invoice-table two-columns\">\n <thead>\n <tr>\n <th>{{ company.name }}</th>\n <th style=\"text-align: right;\">{{ company.company_name_in_arabic }}</th>\n </tr>\n </thead>\n\n <tbody>\n <!-- Invoice Info -->\n <tr>\n <td>Invoice#: {{doc.name}}</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}</td>\n </tr>\n <tr>\n <td>Invoice Date: {{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}</td>\n </tr>\n <tr>\n <td>Date of Supply:{{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}</td>\n </tr>\n \n <!--Supplier Info -->\n <tr>\n <td>Supplier:</td>\n <td>\u0627\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n\t\t{% if (company.tax_id) %}\n <tr>\n <td>Supplier Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n <tr>\n <td>{{ company.tax_id }}</td>\n <td>{{ company.tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td>{{ company.name }}</td>\n <td>{{ company.company_name_in_arabic }} </td>\n </tr>\n \n \n {% if(supplier_address_doc) %}\n <tr>\n <td>{{ supplier_address_doc.address_line1}} </td>\n <td>{{ supplier_address_doc.address_in_arabic}} </td>\n </tr>\n <tr>\n <td>Phone: {{ supplier_address_doc.phone }}</td>\n <td>\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}</td>\n </tr>\n <tr>\n <td>Email: {{ supplier_address_doc.email_id }}</td>\n <td>\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}</td>\n </tr>\n {% endif %}\n \n <!-- Customer Info -->\n <tr>\n <td>CUSTOMER:</td>\n <td>\u0639\u0645\u064a\u0644:</td>\n </tr>\n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n <tr>\n <td>Customer Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:</td>\n </tr>\n <tr>\n <td>{{ customer_tax_id }}</td>\n <td>{{ customer_tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td> {{ doc.customer }}</td>\n <td> {{ doc.customer_name_in_arabic }} </td>\n </tr>\n \n {% if(customer_address) %}\n <tr>\n <td>{{ customer_address.address_line1}} </td>\n <td>{{ customer_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n {% if(customer_shipping_address) %}\n <tr>\n <td>SHIPPING ADDRESS:</td>\n <td>\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:</td>\n </tr>\n \n <tr>\n <td>{{ customer_shipping_address.address_line1}} </td>\n <td>{{ customer_shipping_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n <tr>\n <td>OTHER INFORMATION</td>\n <td>\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649</td>\n </tr>\n \n <tr>\n <td>Purchase Order Number: {{ doc.po_no }}</td>\n <td>\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}</td>\n </tr>\n {% endif %}\n \n <tr>\n <td>Payment Due Date: {{ doc.due_date}} </td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}</td>\n </tr>\n </tbody>\n </table>\n\n <!--Dynamic Colspan for total row columns-->\n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n <!-- Items -->\n {% set total = namespace(amount = 0) %}\n <table class=\"ksa-invoice-table\">\n <thead>\n <tr>\n <th>Nature of goods or services <br />\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a</th>\n <th>\n Unit price <br />\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n </th>\n <th>\n Quantity <br />\n \u0627\u0644\u0643\u0645\u064a\u0629\n </th>\n <th>\n Taxable Amount <br />\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n </th>\n \n {% for row in doc.taxes %}\n <th style=\"min-width: 130px\">{{row.description}}</th>\n {% endfor %}\n \n <th>\n Total <br />\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n </th>\n </tr>\n </thead>\n <tbody>\n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n <tr>\n <td>{{ item.item_code }}</td>\n <td>{{ item.get_formatted(\"rate\") }}</td>\n <td>{{ item.qty }}</td>\n <td>{{ item.get_formatted(\"amount\") }}</td>\n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set tax_amount = frappe.utils.flt(data_object[item.item_code][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n <td>\n <div class=\"qr-flex\">\n {%- if(data_object[item.item_code][0])-%}\n <span>{{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}</span>\n {%- endif -%}\n <span>\n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}</span>\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n </div>\n </td>\n {% endfor %}\n <td>{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}</td>\n </tr>\n {%- endfor -%}\n </tbody>\n <tfoot>\n <tr>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n \n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n <br />\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n </td>\n <td colspan={{ col.two }}>\n Total (Excluding VAT)\n <br />\n Total VAT\n </td>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n </tr>\n <tr>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642</td>\n <td colspan={{ col.two }}>Total Amount Due</td>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n </tr>\n </tfoot>\n </table>\n\n\t{%- if doc.terms -%}\n <p>\n {{doc.terms}}\n </p>\n\t{%- endif -%}\n</div>\n",
+ "html": "<div class=\"ksa-vat-format\">\n <div class=\"qr-flex\">\n <div style=\"qr-flex: 1\">\n <h2 class=\"invoice-heading\">TAX INVOICE</h2>\n <h2 class=\"invoice-heading\">\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629</h2>\n </div>\n \n <img class=\"qr-code\" src={{doc.qr_code}}>\n </div>\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n <table class=\"ksa-invoice-table two-columns\">\n <thead>\n <tr>\n <th>{{ company.name }}</th>\n <th style=\"text-align: right;\">{{ company.company_name_in_arabic }}</th>\n </tr>\n </thead>\n\n <tbody>\n <!-- Invoice Info -->\n <tr>\n <td>Invoice#: {{doc.name}}</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}</td>\n </tr>\n <tr>\n <td>Invoice Date: {{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}</td>\n </tr>\n <tr>\n <td>Date of Supply:{{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}</td>\n </tr>\n \n <!--Supplier Info -->\n <tr>\n <td>Supplier:</td>\n <td>\u0627\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n\t\t{% if (company.tax_id) %}\n <tr>\n <td>Supplier Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n <tr>\n <td>{{ company.tax_id }}</td>\n <td>{{ company.tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td>{{ company.name }}</td>\n <td>{{ company.company_name_in_arabic }} </td>\n </tr>\n \n \n {% if(supplier_address_doc) %}\n <tr>\n <td>{{ supplier_address_doc.address_line1}} </td>\n <td>{{ supplier_address_doc.address_in_arabic}} </td>\n </tr>\n <tr>\n <td>Phone: {{ supplier_address_doc.phone }}</td>\n <td>\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}</td>\n </tr>\n <tr>\n <td>Email: {{ supplier_address_doc.email_id }}</td>\n <td>\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}</td>\n </tr>\n {% endif %}\n \n <!-- Customer Info -->\n <tr>\n <td>CUSTOMER:</td>\n <td>\u0639\u0645\u064a\u0644:</td>\n </tr>\n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n <tr>\n <td>Customer Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:</td>\n </tr>\n <tr>\n <td>{{ customer_tax_id }}</td>\n <td>{{ customer_tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td> {{ doc.customer }}</td>\n <td> {{ doc.customer_name_in_arabic }} </td>\n </tr>\n \n {% if(customer_address) %}\n <tr>\n <td>{{ customer_address.address_line1}} </td>\n <td>{{ customer_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n {% if(customer_shipping_address) %}\n <tr>\n <td>SHIPPING ADDRESS:</td>\n <td>\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:</td>\n </tr>\n \n <tr>\n <td>{{ customer_shipping_address.address_line1}} </td>\n <td>{{ customer_shipping_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n <tr>\n <td>OTHER INFORMATION</td>\n <td>\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649</td>\n </tr>\n \n <tr>\n <td>Purchase Order Number: {{ doc.po_no }}</td>\n <td>\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}</td>\n </tr>\n {% endif %}\n \n <tr>\n <td>Payment Due Date: {{ doc.due_date}} </td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}</td>\n </tr>\n </tbody>\n </table>\n\n <!--Dynamic Colspan for total row columns-->\n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n <!-- Items -->\n {% set total = namespace(amount = 0) %}\n <table class=\"ksa-invoice-table\">\n <thead>\n <tr>\n <th>Nature of goods or services <br />\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a</th>\n <th>\n Unit price <br />\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n </th>\n <th>\n Quantity <br />\n \u0627\u0644\u0643\u0645\u064a\u0629\n </th>\n <th>\n Taxable Amount <br />\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n </th>\n \n {% for row in doc.taxes %}\n <th style=\"min-width: 130px\">{{row.description}}</th>\n {% endfor %}\n \n <th>\n Total <br />\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n </th>\n </tr>\n </thead>\n <tbody>\n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n <tr>\n <td>{{ item.item_code or item.item_name }}</td>\n <td>{{ item.get_formatted(\"rate\") }}</td>\n <td>{{ item.qty }}</td>\n <td>{{ item.get_formatted(\"amount\") }}</td>\n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set key = item.item_code or item.item_name %}\n {% set tax_amount = frappe.utils.flt(data_object[key][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n <td>\n <div class=\"qr-flex\">\n {%- if(data_object[key][0])-%}\n <span>{{ frappe.format(data_object[key][0], {'fieldtype': 'Percent'}) }}</span>\n {%- endif -%}\n <span>\n {%- if(data_object[key][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}</span>\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n </div>\n </td>\n {% endfor %}\n <td>{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}</td>\n </tr>\n {%- endfor -%}\n </tbody>\n <tfoot>\n <tr>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n \n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n <br />\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n </td>\n <td colspan={{ col.two }}>\n Total (Excluding VAT)\n <br />\n Total VAT\n </td>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n </tr>\n <tr>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642</td>\n <td colspan={{ col.two }}>Total Amount Due</td>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n </tr>\n </tfoot>\n </table>\n\n\t{%- if doc.terms -%}\n <p>\n {{doc.terms}}\n </p>\n\t{%- endif -%}\n</div>\n",
"idx": 0,
"line_breaks": 0,
"margin_bottom": 15.0,
"margin_left": 15.0,
"margin_right": 15.0,
"margin_top": 15.0,
- "modified": "2021-11-22 10:40:24.716932",
+ "modified": "2021-11-29 13:47:37.870818",
"modified_by": "Administrator",
"module": "Regional",
"name": "KSA VAT Invoice",
diff --git a/erpnext/regional/report/eway_bill/eway_bill.py b/erpnext/regional/report/eway_bill/eway_bill.py
index 91a4767..f3fe5e8 100644
--- a/erpnext/regional/report/eway_bill/eway_bill.py
+++ b/erpnext/regional/report/eway_bill/eway_bill.py
@@ -106,14 +106,14 @@
row.update({'ship_to_state': row.to_state})
def set_taxes(row, filters):
- taxes = frappe.get_list("Sales Taxes and Charges",
+ taxes = frappe.get_all("Sales Taxes and Charges",
filters={
'parent': row.dn_id
},
fields=('item_wise_tax_detail', 'account_head'))
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={
"parent": "GST Settings",
"company": filters.company
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 5a281a4..17e5064 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -41,7 +41,7 @@
return self.columns, self.data
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")
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")
diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py
index a2f634e..1051315 100644
--- a/erpnext/regional/saudi_arabia/utils.py
+++ b/erpnext/regional/saudi_arabia/utils.py
@@ -1,7 +1,10 @@
import io
import os
+from base64 import b64encode
import frappe
+from frappe import _
+from frappe.utils.data import add_to_date, get_time, getdate
from pyqrcode import create as qr_create
from erpnext import get_region
@@ -28,24 +31,74 @@
for field in meta.get_image_fields():
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
- default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
+ seller_name = frappe.db.get_value(
+ 'Company',
+ doc.company,
+ 'company_name_in_arabic')
- # System Language
- language = frappe.get_system_settings('language')
+ if not seller_name:
+ frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
- params = urlencode({
- 'format': default_print_format or 'Standard',
- '_lang': language,
- 'key': doc.get_signature()
- })
+ tag = bytes([1]).hex()
+ length = bytes([len(seller_name.encode('utf-8'))]).hex()
+ value = seller_name.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
- # creating qr code for the url
- url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
+ # 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()
+
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)
# making file
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 48b1cc5..da9c66d 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -4,6 +4,8 @@
import frappe
from frappe.model.document import Document
+from frappe.query_builder import Case
+from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, nowdate
@@ -19,34 +21,42 @@
- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
def get_first_sle(self):
- sle = frappe.db.sql("""
- select * from `tabStock Ledger Entry`
- where item_code = %s
- and warehouse = %s
- order by timestamp(posting_date, posting_time) asc, creation asc
- limit 1
- """, (self.item_code, self.warehouse), as_dict=1)
- return sle and sle[0] or None
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ first_sle = (
+ frappe.qb.from_(sle)
+ .select("*")
+ .where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse))
+ .orderby(sle.posting_date, sle.posting_time, sle.creation)
+ .limit(1)
+ ).run(as_dict=True)
+
+ return first_sle and first_sle[0] or None
def update_reserved_qty_for_production(self):
'''Update qty reserved for production from Production Item tables
in open work orders'''
- self.reserved_qty_for_production = frappe.db.sql('''
- SELECT
- CASE WHEN ifnull(skip_transfer, 0) = 0 THEN
- SUM(item.required_qty - item.transferred_qty)
- ELSE
- SUM(item.required_qty - item.consumed_qty)
- END
- FROM `tabWork Order` pro, `tabWork Order Item` item
- WHERE
- item.item_code = %s
- and item.parent = pro.name
- and pro.docstatus = 1
- and item.source_warehouse = %s
- and pro.status not in ("Stopped", "Completed")
- and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty)
- ''', (self.item_code, self.warehouse))[0][0]
+
+ wo = frappe.qb.DocType("Work Order")
+ wo_item = frappe.qb.DocType("Work Order Item")
+
+ self.reserved_qty_for_production = (
+ frappe.qb
+ .from_(wo)
+ .from_(wo_item)
+ .select(Case()
+ .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty))
+ .else_(Sum(wo_item.required_qty - wo_item.consumed_qty))
+ )
+ .where(
+ (wo_item.item_code == self.item_code)
+ & (wo_item.parent == wo.name)
+ & (wo.docstatus == 1)
+ & (wo_item.source_warehouse == self.warehouse)
+ & (wo.status.notin(["Stopped", "Completed"]))
+ & ((wo_item.required_qty > wo_item.transferred_qty)
+ | (wo_item.required_qty > wo_item.consumed_qty))
+ )
+ ).run()[0][0] or 0.0
self.set_projected_qty()
@@ -55,36 +65,53 @@
def update_reserved_qty_for_sub_contracting(self):
#reserved qty
- reserved_qty_for_sub_contract = frappe.db.sql('''
- select ifnull(sum(itemsup.required_qty),0)
- from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup
- where
- itemsup.rm_item_code = %s
- and itemsup.parent = po.name
- and po.docstatus = 1
- and po.is_subcontracted = 'Yes'
- and po.status != 'Closed'
- and po.per_received < 100
- and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
- #Get Transferred Entries
- materials_transferred = frappe.db.sql("""
- select
- ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0)
- from
- `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
- where
- se.docstatus=1
- and se.purpose='Send to Subcontractor'
- and ifnull(se.purchase_order, '') !=''
- and (sed.item_code = %(item)s or sed.original_item = %(item)s)
- and se.name = sed.parent
- and se.purchase_order = po.name
- and po.docstatus = 1
- and po.is_subcontracted = 'Yes'
- and po.status != 'Closed'
- and po.per_received < 100
- """, {'item': self.item_code})[0][0]
+ po = frappe.qb.DocType("Purchase Order")
+ supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")
+
+ reserved_qty_for_sub_contract = (
+ frappe.qb
+ .from_(po)
+ .from_(supplied_item)
+ .select(Sum(Coalesce(supplied_item.required_qty, 0)))
+ .where(
+ (supplied_item.rm_item_code == self.item_code)
+ & (po.name == supplied_item.parent)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ & (supplied_item.reserve_warehouse == self.warehouse)
+ )
+ ).run()[0][0] or 0.0
+
+ se = frappe.qb.DocType("Stock Entry")
+ se_item = frappe.qb.DocType("Stock Entry Detail")
+
+ materials_transferred = (
+ frappe.qb
+ .from_(se)
+ .from_(se_item)
+ .from_(po)
+ .select(Sum(
+ Case()
+ .when(se.is_return == 1, se_item.transfer_qty * -1)
+ .else_(se_item.transfer_qty)
+ ))
+ .where(
+ (se.docstatus == 1)
+ & (se.purpose == "Send to Subcontractor")
+ & (Coalesce(se.purchase_order, "") != "")
+ & ((se_item.item_code == self.item_code)
+ | (se_item.original_item == self.item_code))
+ & (se.name == se_item.parent)
+ & (po.name == se.purchase_order)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ )
+ ).run()[0][0] or 0.0
if reserved_qty_for_sub_contract > materials_transferred:
reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred
@@ -160,4 +187,4 @@
'indented_qty': indented_qty,
'planned_qty': planned_qty,
'projected_qty': projected_qty
- })
\ No newline at end of file
+ })
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 5daabe8..c9b8a37 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -222,10 +222,11 @@
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
def validate_website_image(self):
+ """Validate if the website image is a public file"""
+
if frappe.flags.in_import:
return
- """Validate if the website image is a public file"""
auto_set_website_image = False
if not self.website_image and self.image:
auto_set_website_image = True
@@ -255,10 +256,11 @@
self.website_image = None
def make_thumbnail(self):
+ """Make a thumbnail of `website_image`"""
+
if frappe.flags.in_import:
return
- """Make a thumbnail of `website_image`"""
import requests.exceptions
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 7237178..8b1224b 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -488,7 +488,7 @@
item_doc.save()
# Check values saved correctly
- barcodes = frappe.get_list(
+ barcodes = frappe.get_all(
'Item Barcode',
fields=['barcode', 'barcode_type'],
filters={'parent': item_code})
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index e00382b..cd180a4 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -299,7 +299,7 @@
"warehouse": warehouse,
"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) ,
- "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),
'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no,
@@ -317,6 +317,7 @@
"net_rate": 0.0,
"net_amount": 0.0,
"discount_percentage": 0.0,
+ "discount_amount": 0.0,
"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,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,