diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 018c87aa172..3ed37046b08 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.17.0' +__version__ = '12.18.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 3c12f85f938..025b98799e1 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -41,6 +41,8 @@ frappe.ui.form.on('Accounting Dimension', { }); }); } + + frm.toggle_enable('document_type', frm.doc.__islocal); }, document_type: function(frm) { diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 894ec5bdec5..e0d18f297a9 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -29,6 +29,16 @@ class AccountingDimension(Document): if exists and self.is_new(): frappe.throw("Document Type already used as a dimension") + if not self.is_new(): + self.validate_document_type_change() + + def validate_document_type_change(self): + doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type") + if doctype_before_save != self.document_type: + message = _("Cannot change Reference Document Type.") + message += _("Please create a new Accounting Dimension if required.") + frappe.throw(message) + def after_insert(self): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 2f8b634664c..375ec6fcd19 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -88,18 +88,18 @@ class PaymentReconciliation(Document): voucher_type = ('Sales Invoice' if self.party_type == 'Customer' else "Purchase Invoice") - return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, - (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount, + return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type, + (sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, account_currency as currency - FROM `tab{doc}`, `tabGL Entry` + FROM `tab{doc}` doc, `tabGL Entry` gl WHERE - (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) - and `tab{doc}`.{party_type_field} = %(party)s - and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL - and `tabGL Entry`.against_voucher_type = %(voucher_type)s - and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s - and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s - GROUP BY `tab{doc}`.name + (doc.name = gl.against_voucher or doc.name = gl.voucher_no) + and doc.{party_type_field} = %(party)s + and doc.is_return = 1 and ifnull(doc.return_against, "") = "" + and gl.against_voucher_type = %(voucher_type)s + and doc.docstatus = 1 and gl.party = %(party)s + and gl.party_type = %(party_type)s and gl.account = %(account)s + GROUP BY doc.name Having amount > 0 """.format( @@ -303,4 +303,4 @@ def reconcile_dr_cr_note(dr_cr_notes, company): ] }) - jv.submit() \ No newline at end of file + jv.submit() diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index b46de6c85bb..429a9f3591d 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc): validate_disabled(doc) + # Validate with existing taxes and charges template for unique tax category + validate_for_tax_category(doc) + for tax in doc.get("taxes"): validate_taxes_and_charges(tax) validate_inclusive_tax(tax, doc) @@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc): def validate_disabled(doc): if doc.is_default and doc.disabled: frappe.throw(_("Disabled template must not be default template")) + +def validate_for_tax_category(doc): + if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}): + frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category))) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 8cc5cb8e99b..07fc68334a8 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -298,7 +298,8 @@ class Subscription(Document): Returns the `Item`s linked to `Subscription Plan` """ if prorate: - prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) + prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start, + self.generate_invoice_at_period_start) items = [] customer = self.customer @@ -468,11 +469,13 @@ class Subscription(Document): if invoice: return invoice.precision('grand_total') - -def get_prorata_factor(period_end, period_start): - diff = flt(date_diff(nowdate(), period_start) + 1) - plan_days = flt(date_diff(period_end, period_start) + 1) - prorate_factor = diff / plan_days +def get_prorata_factor(period_end, period_start, is_prepaid): + if is_prepaid: + prorate_factor = 1 + else: + diff = flt(date_diff(nowdate(), period_start) + 1) + plan_days = flt(date_diff(period_end, period_start) + 1) + prorate_factor = diff / plan_days return prorate_factor diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 5d73e79035c..c0bc16abedc 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -291,7 +291,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual( flt( - get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start), + get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start, + subscription.generate_invoice_at_period_start), 2), flt(prorate_factor, 2) ) @@ -528,9 +529,7 @@ class TestSubscription(unittest.TestCase): current_inv = subscription.get_current_invoice() self.assertEqual(current_inv.status, "Unpaid") - diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1) - plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1) - prorate_factor = flt(diff / plan_days) + prorate_factor = 1 self.assertEqual(flt(current_inv.grand_total, 2), flt(prorate_factor * 900, 2)) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index ec9be9aa64e..66981763c12 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -156,7 +156,7 @@ {{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }} {{ frappe.utils.fmt_money(0, None, "INR") }} {{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }} - {{ frappe.utils.fmt_money(0, None, "INR") }} + {{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }} {{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }} {{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }} diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 0861b20f14a..79b0a6f30ec 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -15,15 +15,51 @@ def execute(filters=None): return columns, data def get_columns(): - return [ - _("Payment Document") + "::130", - _("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":110", - _("Posting Date") + ":Date:100", - _("Cheque/Reference No") + "::120", - _("Clearance Date") + ":Date:100", - _("Against Account") + ":Link/Account:170", - _("Amount") + ":Currency:120" - ] + columns = [{ + "label": _("Payment Document Type"), + "fieldname": "payment_document_type", + "fieldtype": "Link", + "options": "Doctype", + "width": 130 + }, + { + "label": _("Payment Entry"), + "fieldname": "payment_entry", + "fieldtype": "Dynamic Link", + "options": "payment_document_type", + "width": 140 + }, + { + "label": _("Posting Date"), + "fieldname": "posting_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Cheque/Reference No"), + "fieldname": "cheque_no", + "width": 120 + }, + { + "label": _("Clearance Date"), + "fieldname": "clearance_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Against Account"), + "fieldname": "against", + "fieldtype": "Link", + "options": "Account", + "width": 170 + }, + { + "label": _("Amount"), + "fieldname": "amount", + "width": 120 + }] + + return columns def get_conditions(filters): conditions = "" diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 4a79b6a340e..e15ae5cba9d 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -206,7 +206,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i set_gl_entries_by_account(fiscal_year.year_start_date, fiscal_year.year_end_date, root.lft, root.rgt, filters, - gl_entries_by_account, accounts_by_name, ignore_closing_entries=False) + gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False) calculate_values(accounts_by_name, gl_entries_by_account, companies, fiscal_year, filters) accumulate_values_into_parents(accounts, accounts_by_name, companies) @@ -325,7 +325,7 @@ def prepare_data(accounts, fiscal_year, balance_must_be, companies, company_curr return data def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account, - accounts_by_name, ignore_closing_entries=False): + accounts_by_name, accounts, ignore_closing_entries=False): """Returns a dict like { "account": [gl entries], ... }""" company_lft, company_rgt = frappe.get_cached_value('Company', @@ -368,15 +368,31 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g for entry in gl_entries: key = entry.account_number or entry.account_name - validate_entries(key, entry, accounts_by_name) + validate_entries(key, entry, accounts_by_name, accounts) gl_entries_by_account.setdefault(key, []).append(entry) return gl_entries_by_account -def validate_entries(key, entry, accounts_by_name): +def get_account_details(account): + return frappe.get_cached_value('Account', account, ['name', 'report_type', 'root_type', 'company', + 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1) + +def validate_entries(key, entry, accounts_by_name, accounts): if key not in accounts_by_name: - field = "Account number" if entry.account_number else "Account name" - frappe.throw(_("{0} {1} is not present in the parent company").format(field, key)) + args = get_account_details(entry.account) + + if args.parent_account: + parent_args = get_account_details(args.parent_account) + + args.update({ + 'lft': parent_args.lft + 1, + 'rgt': parent_args.rgt - 1, + 'root_type': parent_args.root_type, + 'report_type': parent_args.report_type + }) + + accounts_by_name.setdefault(key, args) + accounts.append(args) def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions = [] diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index b435a9a53cc..402537802e9 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -54,8 +54,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': item_record.item_name, - 'item_group': item_record.item_group, + 'item_name': item_record.item_name if item_record else d.item_name, + 'item_group': item_record.item_group if item_record else d.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -324,6 +324,7 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index d22111c9f5a..8ebc4d18692 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -53,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': item_record.item_name, - 'item_group': item_record.item_group, + 'item_name': item_record.item_name if item_record else d.item_name, + 'item_group': item_record.item_group if item_record else d.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -390,6 +390,7 @@ def get_items(filters, additional_query_columns): `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, + `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ee07892fe43..220059796ac 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1056,7 +1056,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-09-14 14:36:12.418690", + "modified": "2021-01-22 20:27:11.418690", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index be5763b1d9c..75c28328a00 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -89,7 +89,7 @@ class TestPurchaseOrder(unittest.TestCase): frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) - def test_update_child_qty_rate(self): + def test_update_child(self): mr = make_material_request(qty=10) po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" @@ -119,7 +119,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) - def test_add_new_item_in_update_child_qty_rate(self): + def test_update_child_adding_new_item(self): po = create_purchase_order(do_not_save=1) po.items[0].qty = 4 po.save() @@ -145,7 +145,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(po.status, 'To Receive and Bill') - def test_remove_item_in_update_child_qty_rate(self): + def test_update_child_removing_item(self): po = create_purchase_order(do_not_save=1) po.items[0].qty = 4 po.save() @@ -185,7 +185,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEquals(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') - def test_update_child_qty_rate_perm(self): + def test_update_child_perm(self): po = create_purchase_order(item_code= "_Test Item", qty=4) user = 'test@example.com' diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index b54a585b97f..bef2965bf02 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -108,6 +108,10 @@ class RequestforQuotation(BuyingController): 'link_doctype': 'Supplier', 'link_name': rfq_supplier.supplier }) + contact.append('email_ids', { + 'email_id': user.name, + 'is_primary': 1 + }) if not contact.email_id and not contact.user: contact.email_id = user.name diff --git a/erpnext/change_log/v12/v12_18_0.md b/erpnext/change_log/v12/v12_18_0.md new file mode 100644 index 00000000000..64e5d0761e2 --- /dev/null +++ b/erpnext/change_log/v12/v12_18_0.md @@ -0,0 +1,37 @@ +## ERPNext v12.18.0 Release Notes + +### Enhancements + +- Make patient age translatable ([#24416](https://github.com/frappe/erpnext/pull/24416)) +- Adding UOM, Item Group via Update Items ([#24479](https://github.com/frappe/erpnext/pull/24479)) + +### Fixes + +- Incorrect incoming rate for the sales return ([#24620](https://github.com/frappe/erpnext/pull/24620)) +- Prorata factor fixes in subscription ([#24638](https://github.com/frappe/erpnext/pull/24638)) +- QR code image generation for e-invoicing ([#24422](https://github.com/frappe/erpnext/pull/24422)) +- Validation for disabled warehouse ([#24546](https://github.com/frappe/erpnext/pull/24546)) +- Update total in words after updating items ([#24592](https://github.com/frappe/erpnext/pull/24592)) +- Set contact email in RFQ ([#24486](https://github.com/frappe/erpnext/pull/24486)) +- Plaid client version to support latest API ([#24532](https://github.com/frappe/erpnext/pull/24532)) +- Dynamic Links for reports ([#24461](https://github.com/frappe/erpnext/pull/24461)) +- Use supplied year for IRS 1099 forms ([#24425](https://github.com/frappe/erpnext/pull/24425)) +- Add check for allowing access to european region ([#24393](https://github.com/frappe/erpnext/pull/24393)) +- Item-wise Sales Register item_name error ([#24484](https://github.com/frappe/erpnext/pull/24484)) +- Skip e-invoice generation for non-taxable invoices (India) ([#24569](https://github.com/frappe/erpnext/pull/24569)) +- Issues with packing items ([#24606](https://github.com/frappe/erpnext/pull/24606)) +- Remove max 5 file attachment limit in task ([#24056](https://github.com/frappe/erpnext/pull/24056)) +- Calculate discount amount ([#24511](https://github.com/frappe/erpnext/pull/24511)) +- Validate tax template for tax category ([#24403](https://github.com/frappe/erpnext/pull/24403)) +- Do not validate gstin for exports (India) ([#24564](https://github.com/frappe/erpnext/pull/24564)) +- Stock ageing should not take cancelled stock entries. ([#24438](https://github.com/frappe/erpnext/pull/24438)) +- Discount amount calculation on net total ([#24498](https://github.com/frappe/erpnext/pull/24498)) +- Fetching of standalone cr/dr notes for reconciliation ([#24576](https://github.com/frappe/erpnext/pull/24576)) +- Avoid changing Ref. Doctype in Accounting Dimension after creation ([#24579](https://github.com/frappe/erpnext/pull/24579)) +- Add GST state code for Ladakh (India) ([#24635](https://github.com/frappe/erpnext/pull/24635)) +- Consolidated Financial Statement report not works if child company accounts not present in the parent company ([#24580](https://github.com/frappe/erpnext/pull/24580)) +- Missing Asset Id in the Fixed Asset Register Report ([#24391](https://github.com/frappe/erpnext/pull/24391)) +- e_invoice print format not showing other charges ([#24473](https://github.com/frappe/erpnext/pull/24473)) +- Fix filters for report IRS 1099 ([#24597](https://github.com/frappe/erpnext/pull/24597)) +- Stock ledger entry was not created against stock reconciliation ([#24382](https://github.com/frappe/erpnext/pull/24382)) +- Validate cancellation only if irn generated (India) ([#24609](https://github.com/frappe/erpnext/pull/24609)) \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3c628345a59..f1e16ce59c4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1185,43 +1185,28 @@ def add_taxes_from_tax_template(child_item, parent_doc): }) tax_row.db_insert() -def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): +def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): """ - Returns a Sales Order Item child item containing the default values + Returns a Sales/Purchase Order Item child item containing the default values """ p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) - child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname) + child_item = frappe.new_doc(child_doctype, p_doc, child_docname) item = frappe.get_doc("Item", trans_item.get('item_code')) - child_item.item_code = item.item_code - child_item.item_name = item.item_name - child_item.description = item.description - child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date - child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 - child_item.uom = item.stock_uom - set_child_tax_template_and_map(item, child_item, p_doc) - add_taxes_from_tax_template(child_item, p_doc) - child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) - if not child_item.warehouse: - frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") - .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) - return child_item - - -def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): - """ - Returns a Purchase Order Item child item containing the default values - """ - p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) - child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname) - item = frappe.get_doc("Item", trans_item.get('item_code')) - child_item.item_code = item.item_code - child_item.item_name = item.item_name - child_item.description = item.description - child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date - child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 - child_item.uom = item.stock_uom - child_item.base_rate = 1 # Initiallize value will update in parent validation - child_item.base_amount = 1 # Initiallize value will update in parent validation + for field in ("item_code", "item_name", "description", "item_group"): + child_item.update({field: item.get(field)}) + date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" + child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) + child_item.uom = trans_item.get("uom") or item.stock_uom + conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) + child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor + if child_doctype == "Purchase Order Item": + child_item.base_rate = 1 # Initiallize value will update in parent validation + child_item.base_amount = 1 # Initiallize value will update in parent validation + if child_doctype == "Sales Order Item": + child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) + if not child_item.warehouse: + frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") + .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) set_child_tax_template_and_map(item, child_item, p_doc) add_taxes_from_tax_template(child_item, p_doc) return child_item @@ -1285,8 +1270,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults - return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row) + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): @@ -1316,6 +1301,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate")) prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty")) prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor")) + prev_uom, new_uom = child_item.get("uom"), d.get("uom") if parent_doctype == 'Sales Order': prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") @@ -1324,9 +1310,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil rate_unchanged = prev_rate == new_rate qty_unchanged = prev_qty == new_qty + uom_unchanged = prev_uom == new_uom conversion_factor_unchanged = prev_con_fac == new_con_fac date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc - if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged: + if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged: continue validate_quantity(child_item, d) @@ -1347,6 +1334,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil child_item.conversion_factor = 1 else: child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) + + if d.get("uom"): + child_item.uom = d.get("uom") + conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")) + child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor if d.get("delivery_date") and parent_doctype == 'Sales Order': child_item.delivery_date = d.get('delivery_date') @@ -1388,6 +1380,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.flags.ignore_validate_update_after_submit = True parent.set_qty_as_per_stock_uom() parent.calculate_taxes_and_totals() + parent.set_total_in_words() if parent_doctype == "Sales Order": make_packing_list(parent) parent.set_gross_profit() @@ -1413,6 +1406,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_receiving_percentage() if parent.is_subcontracted == "Yes": parent.update_reserved_qty_for_subcontract() + parent.create_raw_materials_supplied("supplied_items") + parent.save() else: parent.update_reserved_qty() parent.update_project() diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 5b7d76d0f08..123020b914f 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -310,12 +310,27 @@ class SellingController(StockController): if flt(d.conversion_factor)==0.0: d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 return_rate = 0 - if cint(self.is_return) and self.return_against and self.docstatus==1: - against_document_no = (d.get("sales_invoice_item") - if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) + if cint(self.is_return) and self.docstatus==1: + if self.return_against: + against_document_no = (d.get("sales_invoice_item") + if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) - return_rate = self.get_incoming_rate_for_sales_return(d.item_code, - self.return_against, against_document_no) + return_rate = self.get_incoming_rate_for_sales_return(d.item_code, + self.return_against, against_document_no) + else: + # For standalone credit note + args = frappe._dict({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": flt(d.qty), + "serial_no": d.serial_no, + "company": d.company, + "allow_zero_valuation": d.allow_zero_valuation + }) + + return_rate = get_incoming_rate(args) # On cancellation or if return entry submission, make stock ledger entry for # target warehouse first, to update serial no values properly diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 712fd3a51f5..5370f4c571c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -319,12 +319,13 @@ class StockController(AccountsController): return incoming_rate def validate_warehouse(self): - from erpnext.stock.utils import validate_warehouse_company + from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse warehouses = list(set([d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)])) for w in warehouses: + validate_disabled_warehouse(w) validate_warehouse_company(w, self.company) def update_billing_percentage(self, update_modified=True): diff --git a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py index b538a581891..3a9d57d6075 100644 --- a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py +++ b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py @@ -19,15 +19,50 @@ def set_defaut_value_for_filters(filters): if not filters.get('lead_age'): filters["lead_age"] = 60 def get_columns(): - return [ - _("Lead") + ":Link/Lead:100", - _("Name") + "::100", - _("Organization") + "::100", - _("Reference Document") + "::150", - _("Reference Name") + ":Dynamic Link/"+_("Reference Document")+":120", - _("Last Communication") + ":Data:200", - _("Last Communication Date") + ":Date:180" - ] + columns = [{ + "label": _("Lead"), + "fieldname": "lead", + "fieldtype": "Link", + "options": "Lead", + "width": 130 + }, + { + "label": _("Name"), + "fieldname": "name", + "width": 120 + }, + { + "label": _("Organization"), + "fieldname": "organization", + "width": 120 + }, + { + "label": _("Reference Document Type"), + "fieldname": "reference_document_type", + "fieldtype": "Link", + "options": "Doctype", + "width": 100 + }, + { + "label": _("Reference Name"), + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "options": "reference_document_type", + "width": 140 + }, + { + "label": _("Last Communication"), + "fieldname": "last_communication", + "fieldtype": "Data", + "width": 200 + }, + { + "label": _("Last Communication Date"), + "fieldname": "last_communication_date", + "fieldtype": "Date", + "width": 100 + }] + return columns def get_data(filters): lead_details = [] diff --git a/erpnext/education/doctype/question/question.json b/erpnext/education/doctype/question/question.json index b3a161daa07..9cb6cb92f4c 100644 --- a/erpnext/education/doctype/question/question.json +++ b/erpnext/education/doctype/question/question.json @@ -13,7 +13,7 @@ "fields": [ { "fieldname": "question", - "fieldtype": "Small Text", + "fieldtype": "Text Editor", "in_list_view": 1, "label": "Question", "reqd": 1 @@ -34,7 +34,7 @@ "read_only": 1 } ], - "modified": "2019-05-30 18:39:21.880974", + "modified": "2021-01-28 18:39:21.880974", "modified_by": "Administrator", "module": "Education", "name": "Question", @@ -77,4 +77,4 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 66d0e5f77db..5f990cdd034 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -20,7 +20,7 @@ class PlaidConnector(): client_id=self.settings.plaid_client_id, secret=self.settings.get_password("plaid_secret"), environment=self.settings.plaid_env, - api_version="2019-05-29" + api_version="2020-09-14" ) def get_access_token(self, public_token): @@ -29,7 +29,7 @@ class PlaidConnector(): response = self.client.Item.public_token.exchange(public_token) access_token = response["access_token"] return access_token - + def get_token_request(self, update_mode=False): country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"] args = { diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index cf8c9b9ca35..a31cf139358 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -308,7 +308,7 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; // List Stock items diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index b60e70fd766..0c3b67a6126 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -293,5 +293,5 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index 9984b0ae9c9..07e13ff4cfd 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -43,7 +43,7 @@ frappe.ui.form.on('Patient', { $(frm.fields_dict['age_html'].wrapper).html(""); } if(frm.doc.dob){ - $(frm.fields_dict['age_html'].wrapper).html("AGE : " + get_age(frm.doc.dob)); + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); } } }); @@ -58,7 +58,7 @@ frappe.ui.form.on("Patient", "dob", function(frm) { } else{ var age_str = get_age(frm.doc.dob); - $(frm.fields_dict['age_html'].wrapper).html("AGE : " + age_str); + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); } } else { @@ -81,7 +81,7 @@ var get_age = function (birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; var btn_create_vital_signs = function (frm) { diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 420416809dd..fc01e50ff09 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -58,7 +58,7 @@ class Patient(Document): if self.dob: born = getdate(self.dob) age = dateutil.relativedelta.relativedelta(getdate(), born) - age_str = str(age.years) + " year(s) " + str(age.months) + " month(s) " + str(age.days) + " day(s)" + age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") return age_str def invoice_patient_registration(self): diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 0a208128564..3ca084ec65f 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -468,5 +468,5 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index e6ceb5c0fc3..94e79fb62c1 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -311,5 +311,5 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.js b/erpnext/healthcare/doctype/sample_collection/sample_collection.js index 9934ce48451..32b15aba912 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.js +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.js @@ -36,5 +36,5 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 08eda7e2c43..b40549c5ba3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -680,3 +680,4 @@ erpnext.patches.v12_0.update_leave_application_status erpnext.patches.v12_0.update_payment_entry_status erpnext.patches.v12_0.add_transporter_address_field #2020-10-27 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 +erpnext.patches.v12_0.add_state_code_for_ladakh diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py new file mode 100644 index 00000000000..d41101cc46a --- /dev/null +++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py @@ -0,0 +1,16 @@ +import frappe +from erpnext.regional.india import states + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = ['Address-gst_state', 'Tax Category-gst_state'] + + # Update options in gst_state custom fields + for field in custom_fields: + gst_state_field = frappe.get_doc('Custom Field', field) + gst_state_field.options = '\n'.join(states) + gst_state_field.save() diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json old mode 100644 new mode 100755 index 8d4552af518..19e210afcf1 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -1,398 +1,398 @@ { - "actions": [], - "allow_import": 1, - "autoname": "TASK-.YYYY.-.#####", - "creation": "2013-01-29 19:25:50", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "subject", - "project", - "issue", - "type", - "is_group", - "column_break0", - "status", - "priority", - "task_weight", - "completed_by", - "color", - "parent_task", - "sb_timeline", - "exp_start_date", - "expected_time", - "column_break_11", - "exp_end_date", - "progress", - "is_milestone", - "sb_details", - "description", - "sb_depends_on", - "depends_on", - "depends_on_tasks", - "sb_actual", - "act_start_date", - "actual_time", - "column_break_15", - "act_end_date", - "sb_costing", - "total_costing_amount", - "total_expense_claim", - "column_break_20", - "total_billing_amount", - "sb_more_info", - "review_date", - "closing_date", - "column_break_22", - "department", - "company", - "lft", - "rgt", - "old_parent" - ], - "fields": [ - { - "fieldname": "subject", - "fieldtype": "Data", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Subject", - "reqd": 1, - "search_index": 1 - }, - { - "bold": 1, - "fieldname": "project", - "fieldtype": "Link", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Project", - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project", - "remember_last_selected_value": 1, - "search_index": 1 - }, - { - "fieldname": "issue", - "fieldtype": "Link", - "label": "Issue", - "options": "Issue" - }, - { - "fieldname": "type", - "fieldtype": "Link", - "label": "Type", - "options": "Task Type" - }, - { - "bold": 1, - "default": "0", - "fieldname": "is_group", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Group" - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, - { - "bold": 1, - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled" - }, - { - "fieldname": "priority", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Priority", - "oldfieldname": "priority", - "oldfieldtype": "Select", - "options": "Low\nMedium\nHigh\nUrgent", - "search_index": 1 - }, - { - "fieldname": "color", - "fieldtype": "Color", - "label": "Color" - }, - { - "bold": 1, - "fieldname": "parent_task", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Parent Task", - "options": "Task", - "search_index": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.__islocal", - "fieldname": "sb_timeline", - "fieldtype": "Section Break", - "label": "Timeline" - }, - { - "fieldname": "exp_start_date", - "fieldtype": "Date", - "label": "Expected Start Date", - "oldfieldname": "exp_start_date", - "oldfieldtype": "Date" - }, - { - "default": "0", - "fieldname": "expected_time", - "fieldtype": "Float", - "label": "Expected Time (in hours)", - "oldfieldname": "exp_total_hrs", - "oldfieldtype": "Data" - }, - { - "fetch_from": "type.weight", - "fieldname": "task_weight", - "fieldtype": "Float", - "label": "Weight" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "bold": 1, - "fieldname": "exp_end_date", - "fieldtype": "Date", - "label": "Expected End Date", - "oldfieldname": "exp_end_date", - "oldfieldtype": "Date", - "search_index": 1 - }, - { - "fieldname": "progress", - "fieldtype": "Percent", - "label": "% Progress", - "no_copy": 1 - }, - { - "default": "0", - "fieldname": "is_milestone", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Milestone" - }, - { - "fieldname": "sb_details", - "fieldtype": "Section Break", - "label": "Details", - "oldfieldtype": "Section Break" - }, - { - "fieldname": "description", - "fieldtype": "Text Editor", - "in_preview": 1, - "label": "Task Description", - "oldfieldname": "description", - "oldfieldtype": "Text Editor", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "sb_depends_on", - "fieldtype": "Section Break", - "label": "Dependencies", - "oldfieldtype": "Section Break" - }, - { - "fieldname": "depends_on", - "fieldtype": "Table", - "label": "Dependent Tasks", - "options": "Task Depends On" - }, - { - "fieldname": "depends_on_tasks", - "fieldtype": "Code", - "hidden": 1, - "label": "Depends on Tasks", - "read_only": 1 - }, - { - "fieldname": "sb_actual", - "fieldtype": "Section Break", - "oldfieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, - { - "fieldname": "act_start_date", - "fieldtype": "Date", - "label": "Actual Start Date (via Time Sheet)", - "oldfieldname": "act_start_date", - "oldfieldtype": "Date", - "read_only": 1 - }, - { - "fieldname": "actual_time", - "fieldtype": "Float", - "label": "Actual Time (in hours)", - "read_only": 1 - }, - { - "fieldname": "column_break_15", - "fieldtype": "Column Break" - }, - { - "fieldname": "act_end_date", - "fieldtype": "Date", - "label": "Actual End Date (via Time Sheet)", - "oldfieldname": "act_end_date", - "oldfieldtype": "Date", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "sb_costing", - "fieldtype": "Section Break", - "label": "Costing" - }, - { - "fieldname": "total_costing_amount", - "fieldtype": "Currency", - "label": "Total Costing Amount (via Time Sheet)", - "oldfieldname": "actual_budget", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "total_expense_claim", - "fieldtype": "Currency", - "label": "Total Expense Claim (via Expense Claim)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break" - }, - { - "fieldname": "total_billing_amount", - "fieldtype": "Currency", - "label": "Total Billing Amount (via Time Sheet)", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "sb_more_info", - "fieldtype": "Section Break", - "label": "More Info" - }, - { - "depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"", - "fieldname": "review_date", - "fieldtype": "Date", - "label": "Review Date", - "oldfieldname": "review_date", - "oldfieldtype": "Date" - }, - { - "depends_on": "eval:doc.status == \"Closed\"", - "fieldname": "closing_date", - "fieldtype": "Date", - "label": "Closing Date", - "oldfieldname": "closing_date", - "oldfieldtype": "Date" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Department" - }, - { - "fetch_from": "project.company", - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1 - }, - { - "fieldname": "lft", - "fieldtype": "Int", - "hidden": 1, - "label": "lft", - "read_only": 1 - }, - { - "fieldname": "rgt", - "fieldtype": "Int", - "hidden": 1, - "label": "rgt", - "read_only": 1 - }, - { - "fieldname": "old_parent", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 1, - "label": "Old Parent", - "read_only": 1 - }, - { - "fieldname": "completed_by", - "fieldtype": "Link", - "label": "Completed By", - "no_copy": 1, - "options": "User" - } - ], - "icon": "fa fa-check", - "idx": 1, - "is_tree": 1, - "links": [], - "max_attachments": 5, - "modified": "2020-07-03 12:36:04.960457", - "modified_by": "Administrator", - "module": "Projects", - "name": "Task", - "nsm_parent_field": "parent_task", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "share": 1, - "write": 1 - } - ], - "search_fields": "subject", - "show_name_in_global_search": 1, - "show_preview_popup": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "project", - "title_field": "subject", - "track_seen": 1 + "actions": [], + "allow_import": 1, + "autoname": "TASK-.YYYY.-.#####", + "creation": "2013-01-29 19:25:50", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "subject", + "project", + "issue", + "type", + "is_group", + "column_break0", + "status", + "priority", + "task_weight", + "completed_by", + "color", + "parent_task", + "sb_timeline", + "exp_start_date", + "expected_time", + "column_break_11", + "exp_end_date", + "progress", + "is_milestone", + "sb_details", + "description", + "sb_depends_on", + "depends_on", + "depends_on_tasks", + "sb_actual", + "act_start_date", + "actual_time", + "column_break_15", + "act_end_date", + "sb_costing", + "total_costing_amount", + "total_expense_claim", + "column_break_20", + "total_billing_amount", + "sb_more_info", + "review_date", + "closing_date", + "column_break_22", + "department", + "company", + "lft", + "rgt", + "old_parent" + ], + "fields": [ + { + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Subject", + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fieldname": "project", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project", + "remember_last_selected_value": 1, + "search_index": 1 + }, + { + "fieldname": "issue", + "fieldtype": "Link", + "label": "Issue", + "options": "Issue" + }, + { + "fieldname": "type", + "fieldtype": "Link", + "label": "Type", + "options": "Task Type" + }, + { + "bold": 1, + "default": "0", + "fieldname": "is_group", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Group" + }, + { + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "bold": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled" + }, + { + "fieldname": "priority", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Priority", + "oldfieldname": "priority", + "oldfieldtype": "Select", + "options": "Low\nMedium\nHigh\nUrgent", + "search_index": 1 + }, + { + "fieldname": "color", + "fieldtype": "Color", + "label": "Color" + }, + { + "bold": 1, + "fieldname": "parent_task", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Parent Task", + "options": "Task", + "search_index": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.__islocal", + "fieldname": "sb_timeline", + "fieldtype": "Section Break", + "label": "Timeline" + }, + { + "fieldname": "exp_start_date", + "fieldtype": "Date", + "label": "Expected Start Date", + "oldfieldname": "exp_start_date", + "oldfieldtype": "Date" + }, + { + "default": "0", + "fieldname": "expected_time", + "fieldtype": "Float", + "label": "Expected Time (in hours)", + "oldfieldname": "exp_total_hrs", + "oldfieldtype": "Data" + }, + { + "fetch_from": "type.weight", + "fieldname": "task_weight", + "fieldtype": "Float", + "label": "Weight" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "fieldname": "exp_end_date", + "fieldtype": "Date", + "label": "Expected End Date", + "oldfieldname": "exp_end_date", + "oldfieldtype": "Date", + "search_index": 1 + }, + { + "fieldname": "progress", + "fieldtype": "Percent", + "label": "% Progress", + "no_copy": 1 + }, + { + "default": "0", + "fieldname": "is_milestone", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Milestone" + }, + { + "fieldname": "sb_details", + "fieldtype": "Section Break", + "label": "Details", + "oldfieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "in_preview": 1, + "label": "Task Description", + "oldfieldname": "description", + "oldfieldtype": "Text Editor", + "print_width": "300px", + "width": "300px" + }, + { + "fieldname": "sb_depends_on", + "fieldtype": "Section Break", + "label": "Dependencies", + "oldfieldtype": "Section Break" + }, + { + "fieldname": "depends_on", + "fieldtype": "Table", + "label": "Dependent Tasks", + "options": "Task Depends On" + }, + { + "fieldname": "depends_on_tasks", + "fieldtype": "Code", + "hidden": 1, + "label": "Depends on Tasks", + "read_only": 1 + }, + { + "fieldname": "sb_actual", + "fieldtype": "Section Break", + "oldfieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "act_start_date", + "fieldtype": "Date", + "label": "Actual Start Date (via Time Sheet)", + "oldfieldname": "act_start_date", + "oldfieldtype": "Date", + "read_only": 1 + }, + { + "fieldname": "actual_time", + "fieldtype": "Float", + "label": "Actual Time (in hours)", + "read_only": 1 + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "act_end_date", + "fieldtype": "Date", + "label": "Actual End Date (via Time Sheet)", + "oldfieldname": "act_end_date", + "oldfieldtype": "Date", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_costing", + "fieldtype": "Section Break", + "label": "Costing" + }, + { + "fieldname": "total_costing_amount", + "fieldtype": "Currency", + "label": "Total Costing Amount (via Time Sheet)", + "oldfieldname": "actual_budget", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "total_expense_claim", + "fieldtype": "Currency", + "label": "Total Expense Claim (via Expense Claim)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_billing_amount", + "fieldtype": "Currency", + "label": "Total Billing Amount (via Time Sheet)", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_more_info", + "fieldtype": "Section Break", + "label": "More Info" + }, + { + "depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"", + "fieldname": "review_date", + "fieldtype": "Date", + "label": "Review Date", + "oldfieldname": "review_date", + "oldfieldtype": "Date" + }, + { + "depends_on": "eval:doc.status == \"Closed\"", + "fieldname": "closing_date", + "fieldtype": "Date", + "label": "Closing Date", + "oldfieldname": "closing_date", + "oldfieldtype": "Date" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department" + }, + { + "fetch_from": "project.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1 + }, + { + "fieldname": "lft", + "fieldtype": "Int", + "hidden": 1, + "label": "lft", + "read_only": 1 + }, + { + "fieldname": "rgt", + "fieldtype": "Int", + "hidden": 1, + "label": "rgt", + "read_only": 1 + }, + { + "fieldname": "old_parent", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "Old Parent", + "read_only": 1 + }, + { + "fieldname": "completed_by", + "fieldtype": "Link", + "label": "Completed By", + "no_copy": 1, + "options": "User" + } + ], + "icon": "fa fa-check", + "idx": 1, + "is_tree": 1, + "links": [], + "max_attachments": 0, + "modified": "2020-07-03 12:36:04.960457", + "modified_by": "Administrator", + "module": "Projects", + "name": "Task", + "nsm_parent_field": "parent_task", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1, + "write": 1 + } + ], + "search_fields": "subject", + "show_name_in_global_search": 1, + "show_preview_popup": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "project", + "title_field": "subject", + "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 8281bd98e4a..b30db7cb1d8 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -24,6 +24,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if (item.discount_amount) { item.rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item)); + item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin); } }, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6e2409cf875..4547fc97a31 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -469,6 +469,33 @@ erpnext.utils.update_child_items = function(opts) { read_only: 0, disabled: 0, label: __('Item Code') + }, { + fieldtype:'Link', + fieldname:'uom', + options: 'UOM', + read_only: 0, + label: __('UOM'), + reqd: 1, + onchange: function () { + frappe.call({ + method: "erpnext.stock.get_item_details.get_conversion_factor", + args: { item_code: this.doc.item_code, uom: this.value }, + callback: r => { + if(!r.exc) { + if (this.doc.conversion_factor == r.message.conversion_factor) return; + + const docname = this.doc.docname; + dialog.fields_dict.trans_items.df.data.some(doc => { + if (doc.docname == docname) { + doc.conversion_factor = r.message.conversion_factor; + dialog.fields_dict.trans_items.grid.refresh(); + return true; + } + }); + } + } + }); + }, }, { fieldtype:'Float', fieldname:"qty", @@ -552,6 +579,7 @@ erpnext.utils.update_child_items = function(opts) { "conversion_factor": d.conversion_factor, "qty": d.qty, "rate": d.rate, + "uom": d.uom }); this.data = dialog.fields_dict.trans_items.df.data; dialog.fields_dict.trans_items.grid.refresh(); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 7bd0a72e311..2d563fa3e0a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -133,6 +133,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ () => me.update_batch_serial_no_items(), () => { refresh_field("items"); + refresh_field("packed_items"); if (me.callback) { return me.callback(me.item); } @@ -147,7 +148,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ if (this.item.serial_no) { this.dialog.fields_dict.serial_no.set_value(this.item.serial_no); } - + if (this.has_batch && !this.has_serial_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { @@ -229,7 +230,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.map_row_values(row, batch, 'batch_no', 'selected_qty', this.values.warehouse); }); - } + } }, update_serial_no_item() { @@ -248,7 +249,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ filters: { 'name': ["in", selected_serial_nos]}, fields: ["batch_no", "name"] }).then((data) => { - // data = [{batch_no: 'batch-1', name: "SR-001"}, + // data = [{batch_no: 'batch-1', name: "SR-001"}, // {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}] const batch_serial_map = data.reduce((acc, d) => { if (!acc[d['batch_no']]) acc[d['batch_no']] = []; @@ -296,6 +297,8 @@ erpnext.SerialNoBatchSelector = Class.extend({ } else { row.warehouse = values.warehouse || warehouse; } + + this.frm.dirty(); }, update_total_qty: function() { diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index d6221a80aa2..378b735e078 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -20,6 +20,7 @@ states = [ 'Jharkhand', 'Karnataka', 'Kerala', + 'Ladakh', 'Lakshadweep Islands', 'Madhya Pradesh', 'Maharashtra', @@ -59,6 +60,7 @@ state_numbers = { "Jharkhand": "20", "Karnataka": "29", "Kerala": "32", + "Ladakh": "38", "Lakshadweep Islands": "31", "Madhya Pradesh": "23", "Maharashtra": "27", @@ -80,4 +82,4 @@ state_numbers = { "West Bengal": "19", } -number_state_mapping = {v: k for k, v in iteritems(state_numbers)} \ No newline at end of file +number_state_mapping = {v: k for k, v in iteritems(state_numbers)} diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 5ecbb0ff502..9fa1334840d 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -22,6 +22,9 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!irn && !__unsaved) { const action = () => { + if (frm.doc.__unsaved) { + frappe.throw(__('Please save the document to generate IRN.')); + } frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', args: { doctype, docname: name }, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 79d554c0f96..14fcb9de897 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -11,6 +11,7 @@ import json import base64 import frappe import traceback +import io from frappe import _, bold from pyqrcode import create as qrcreate from frappe.integrations.utils import make_post_request, make_get_request @@ -19,11 +20,13 @@ from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_d def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) - invalid_doctype = doc.doctype not in ['Sales Invoice'] + invalid_doctype = doc.doctype != 'Sales Invoice' invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') + no_taxes_applied = len(doc.get('taxes', [])) == 0 - if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return + if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: + return if doc.docstatus == 0 and doc._action == 'save': if doc.irn: @@ -34,7 +37,7 @@ def validate_einvoice_fields(doc): elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) - elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: + elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) def raise_document_name_too_long_error(): @@ -160,7 +163,7 @@ def get_item_list(invoice): item.description = d.item_name.replace('"', '\\"') item.qty = abs(item.qty) - item.discount_amount = abs(item.discount_amount * item.qty) + item.discount_amount = 0 item.unit_rate = abs(item.base_net_amount / item.qty) item.gross_amount = abs(item.base_net_amount) item.taxable_value = abs(item.base_net_amount) @@ -220,11 +223,12 @@ def get_invoice_value_details(invoice): if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: invoice_value_details.base_total = abs(invoice.base_total) + invoice_value_details.invoice_discount_amt = invoice.base_discount_amount else: invoice_value_details.base_total = abs(invoice.base_net_total) + # since tax already considers discount amount + invoice_value_details.invoice_discount_amt = 0 - # since tax already considers discount amount - invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) @@ -301,7 +305,7 @@ def validate_mandatory_fields(invoice): _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), title=_('Missing Fields') ) - if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): frappe.throw( _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), title=_('Missing Fields') @@ -328,7 +332,10 @@ def make_einvoice(invoice): shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: - shipping_details = get_party_details(invoice.shipping_address_name) + if invoice.gst_category == 'Overseas': + shipping_details = get_overseas_address_details(invoice.shipping_address_name) + else: + shipping_details = get_party_details(invoice.shipping_address_name) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) @@ -440,7 +447,7 @@ class GSPConnector(): self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' - self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' + self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' def get_credentials(self): @@ -675,6 +682,8 @@ class GSPConnector(): 'cancelRsnCode': reason, 'cancelRmrk': remark }, indent=4) + headers["username"] = headers["user_name"] + del headers["user_name"] try: res = self.make_request('post', self.cancel_ewaybill_url, headers, data) @@ -773,20 +782,21 @@ class GSPConnector(): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype docname = self.invoice.name + filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__") - _file = frappe.new_doc('File') - _file.update({ - 'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')), - 'attached_to_doctype': doctype, - 'attached_to_name': docname, - 'content': str(base64.b64encode(os.urandom(64))), - 'is_private': 1 - }) - _file.insert() - frappe.db.commit() + qr_image = io.BytesIO() url = qrcreate(qrcode, error='L') - abs_file_path = os.path.abspath(_file.get_full_path()) - url.png(abs_file_path, scale=2, quiet_zone=1) + url.png(qr_image, scale=2, quiet_zone=1) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": "qrcode_image", + "is_private": 1, + "content": qr_image.getvalue()}) + _file.save() + frappe.db.commit() self.invoice.qrcode_image = _file.file_url diff --git a/erpnext/regional/india/gst_state_code_data.json b/erpnext/regional/india/gst_state_code_data.json index ff88e0f9d6c..8481c279728 100644 --- a/erpnext/regional/india/gst_state_code_data.json +++ b/erpnext/regional/india/gst_state_code_data.json @@ -168,5 +168,10 @@ "state_number": "37", "state_code": "AD", "state_name": "Andhra Pradesh (New)" + }, + { + "state_number": "38", + "state_code": "LA", + "state_name": "Ladakh" } ] diff --git a/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json b/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json index ce8c44a9a19..e59700f5a5e 100644 --- a/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json +++ b/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json @@ -1,23 +1,26 @@ -[ - { - "align_labels_right": 0, - "css": "", - "custom_format": 1, - "default_print_language": "en", - "disabled": 0, - "doc_type": "Supplier", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

TAX Invoice
{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name\", \"label\": \"Customer Name\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name_in_arabic\", \"label\": \"Customer Name in Arabic\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"posting_date\", \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"company_trn\", \"label\": \"Company TRN\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company_address_display\", \"label\": \"Company Address\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"200px\"}, {\"print_hide\": 0, \"fieldname\": \"uom\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_code\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"items\", \"label\": \"Items\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"charge_type\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"row_id\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"account_head\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"cost_center\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"300px\"}, {\"print_hide\": 0, \"fieldname\": \"rate\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"item_wise_tax_detail\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"taxes\", \"label\": \"Sales Taxes and Charges\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"grand_total\", \"label\": \"Grand Total\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\", \"label\": \"Rounded Total\"}, {\"print_hide\": 0, \"fieldname\": \"in_words\", \"align\": \"left\", \"label\": \"In Words\"}]", - "html": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n
PAYER'S name, street address, city or town, state or province, country, ZIP
or foreign postal code, and telephone no.
\n\t{{company if company else \"\"}}
\n\t{{payer_street_address if payer_street_address else \"\"}}\n
1 RentsOMB No. 1545-0115
2018
Form 1099-MISC
Miscellaneous Income
2 Royalties
3 Other Income
\n\t{{payments if payments else \"\"}}\n\t
4 Federal Income tax withheldCopy A
For
Internal Revenue
Service Center

File with Form 1096
PAYER'S TIN
\n\t{{company_tin if company_tin else \"\"}}\n\t
RECIPIENT'S TIN

\n {{tax_id if tax_id else \"None\"}}\n
Fishing boat proceeds6 Medical and health care payments
RECIPIENT'S name
\n {{supplier if supplier else \"\"}}\n
7 Nonemployee compensation
\n\t
Substitute payments in lieu of dividends or interestFor Privacy Act
and Paperwork
Reduction Act
Notice, see the
2018 General
Instructions for
Certain
Information
Returns.
Street address (including apt. no.)
\n\t{{recipient_street_address if recipient_street_address else \"\"}}\n\t
$___________$___________
9 Payer made direct sales of
$5,000 or more of consumer products
to a buyer
(recipient) for resale
10 Crop insurance proceeds
City or town, state or province, country, and ZIP or foreign postal code
\n\t{{recipient_city_state if recipient_city_state else \"\"}}\n
$___________
1112
Account number (see instructions)FACTA filing
requirement
2nd TIN not.13 Excess golden parachute payments
$___________
14 Gross proceeds paid to an
attorney
$___________
15a Section 409A deferrals15b Section 409 income16 State tax withheld17 State/Payer's state no.18 State income
$$$$
Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the Treasury - Internal Revenue Service
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {{supplier if supplier else \"\"}}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n
PAYER'S name, street address, city or town, state or province, country, ZIP
or foreign postal code, and telephone no.
\n {{company if company else \"\"}}
\n \t{{payer_street_address if payer_street_address else \"\"}}
1 RentsOMB No. 1545-0115
2018
Form 1099-MISC
Miscellaneous Income
2 Royalties
3 Other Income
\n\t{{payments if payments else \"\"}}\n\t
4 Federal Income tax withheldCopy 1
For State Tax
Department
PAYER'S TIN
\n\t{{company_tin if company_tin else \"\"}}\n\t
RECIPIENT'S TIN
\n\t{{tax_id if tax_id else \"\"}}\n\t
Fishing boat proceeds6 Medical and health care payments
RECIPIENT'S name7 Nonemployee compensation
\n\t
Substitute payments in lieu of dividends or interest
Street address (including apt. no.)
\n\t{{recipient_street_address if recipient_street_address else \"\"}}\n\t
$___________$___________
9 Payer made direct sales of
$5,000 or more of consumer products
to a buyer
(recipient) for resale
10 Crop insurance proceeds
City or town, state or province, country, and ZIP or foreign postal code
\n\t{{recipient_city_state if recipient_city_state else \"\"}}\n\t
$___________
1112
Account number (see instructions)FACTA filing
requirement
2nd TIN not.13 Excess golden parachute payments
$___________
14 Gross proceeds paid to an
attorney
$___________
15a Section 409A deferrals15b Section 409 income16 State tax withheld17 State/Payer's state no.18 State income
$$$$
Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the Treasury - Internal Revenue Service
\n
\n", - "line_breaks": 0, - "modified": "2018-10-08 14:56:56.912851", - "module": "Regional", - "name": "IRS 1099 Form", - "print_format_builder": 1, - "print_format_type": "Server", - "show_section_headings": 0, - "standard": "No" - } -] +{ + "align_labels_right": 0, + "creation": "2020-11-09 16:01:26.096002", + "css": "", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Supplier", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

TAX Invoice
{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name\", \"label\": \"Customer Name\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name_in_arabic\", \"label\": \"Customer Name in Arabic\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"posting_date\", \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"company_trn\", \"label\": \"Company TRN\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company_address_display\", \"label\": \"Company Address\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"200px\"}, {\"print_hide\": 0, \"fieldname\": \"uom\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_code\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"items\", \"label\": \"Items\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"charge_type\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"row_id\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"account_head\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"cost_center\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"300px\"}, {\"print_hide\": 0, \"fieldname\": \"rate\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"item_wise_tax_detail\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"taxes\", \"label\": \"Sales Taxes and Charges\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"grand_total\", \"label\": \"Grand Total\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\", \"label\": \"Rounded Total\"}, {\"print_hide\": 0, \"fieldname\": \"in_words\", \"align\": \"left\", \"label\": \"In Words\"}]", + "html": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n
PAYER'S name, street address,\n city or town, state or province, country, ZIP
or foreign postal code, and telephone no.
\n {{ company or \"\" }}
\n {{ payer_street_address or \"\" }}\n
1 RentsOMB No. 1545-0115
\n {{ fiscal_year[:2] }}\n {{ fiscal_year[-2:] }}
Form 1099-MISC\n
Miscellaneous Income
2 Royalties
3 Other Income
{{ payments or \"\" }}
4 Federal Income tax withheldCopy A
For
Internal Revenue
Service\n Center

File with Form 1096
PAYER'S TIN
{{ company_tin or \"\" }}
RECIPIENT'S TIN

{{ tax_id or \"None\" }}
Fishing boat proceeds6 Medical and health care payments
RECIPIENT'S name
{{ supplier or \"\" }}
7 Nonemployee compensation
\n
Substitute payments in lieu of dividends or interestFor Privacy Act
and Paperwork
Reduction Act
Notice, see\n the
2018 General
Instructions for
Certain
Information
Returns.
Street address (including apt. no.)
\n {{ recipient_street_address or \"\" }}\n
$___________$___________
9 Payer made direct sales of
$5,000 or more of consumer\n products
to a buyer
(recipient) for resale
10 Crop insurance proceeds
City or town, state or province, country, and ZIP or\n foreign postal code
\n {{ recipient_city_state or \"\" }}\n
$___________
1112
Account number (see instructions)FACTA filing
requirement
2nd TIN not.13 Excess golden parachute payments
$___________
14 Gross proceeds paid to an
attorney
$___________
15a Section 409A deferrals15b Section 409 income16 State tax withheld17 State/Payer's state no.18 State income
$$$$
Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the\n Treasury - Internal Revenue Service
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {{ supplier or \"\" }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n
PAYER'S name, street address,\n city or town, state or province, country, ZIP
or foreign postal code, and telephone no.
\n {{ company or \"\"}}\n {{ payer_street_address or \"\" }}\n
1 RentsOMB No. 1545-0115
\n {{ fiscal_year[:2] }}\n {{ fiscal_year[-2:] }}
Form 1099-MISC\n
Miscellaneous Income
2 Royalties
3 Other Income
\n {{ payments or \"\" }}\n
4 Federal Income tax withheldCopy 1
For State Tax
Department
PAYER'S TIN
\n {{ company_tin or \"\" }}\n
RECIPIENT'S TIN
\n {{ tax_id or \"\" }}\n
Fishing boat proceeds6 Medical and health care payments
RECIPIENT'S name7 Nonemployee compensation
\n
Substitute payments in lieu of dividends or interest
Street address (including apt. no.)
\n {{ recipient_street_address or \"\" }}\n
$___________$___________
9 Payer made direct sales of
$5,000 or more of consumer\n products
to a buyer
(recipient) for resale
10 Crop insurance proceeds
City or town, state or province, country, and ZIP or\n foreign postal code
\n {{ recipient_city_state or \"\" }}\n
$___________
1112
Account number (see instructions)FACTA filing
requirement
2nd TIN not.13 Excess golden parachute payments
$___________
14 Gross proceeds paid to an
attorney
$___________
15a Section 409A deferrals15b Section 409 income16 State tax withheld17 State/Payer's state no.18 State income
$$$$
Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the\n Treasury - Internal Revenue Service
\n
\n", + "idx": 0, + "line_breaks": 0, + "modified": "2021-01-19 07:25:16.333666", + "modified_by": "Administrator", + "module": "Regional", + "name": "IRS 1099 Form", + "owner": "Administrator", + "print_format_builder": 1, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "No" +} \ No newline at end of file diff --git a/erpnext/regional/report/irs_1099/irs_1099.js b/erpnext/regional/report/irs_1099/irs_1099.js index 2d74652cfe2..070ff43f78c 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.js +++ b/erpnext/regional/report/irs_1099/irs_1099.js @@ -4,7 +4,7 @@ frappe.query_reports["IRS 1099"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -13,7 +13,7 @@ frappe.query_reports["IRS 1099"] = { "width": 80, }, { - "fieldname":"fiscal_year", + "fieldname": "fiscal_year", "label": __("Fiscal Year"), "fieldtype": "Link", "options": "Fiscal Year", @@ -22,7 +22,7 @@ frappe.query_reports["IRS 1099"] = { "width": 80, }, { - "fieldname":"supplier_group", + "fieldname": "supplier_group", "label": __("Supplier Group"), "fieldtype": "Link", "options": "Supplier Group", @@ -32,16 +32,16 @@ frappe.query_reports["IRS 1099"] = { }, ], - onload: function(query_report) { + onload: function (query_report) { query_report.page.add_inner_button(__("Print IRS 1099 Forms"), () => { build_1099_print(query_report); }); } }; -function build_1099_print(query_report){ +function build_1099_print(query_report) { let filters = JSON.stringify(query_report.get_values()); let w = window.open('/api/method/erpnext.regional.report.irs_1099.irs_1099.irs_1099_print?' + - '&filters=' + encodeURIComponent(filters)); + '&filters=' + encodeURIComponent(filters)); // w.print(); } diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index ceea8460bea..c8b953139f1 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -1,31 +1,41 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe import json -from frappe import _, _dict -from frappe.utils import nowdate -from frappe.utils.data import fmt_money -from erpnext.accounts.utils import get_fiscal_year + from PyPDF2 import PdfFileWriter + +import frappe +from erpnext.accounts.utils import get_fiscal_year +from frappe import _ +from frappe.utils import cstr, nowdate +from frappe.utils.data import fmt_money +from frappe.utils.jinja import render_template from frappe.utils.pdf import get_pdf from frappe.utils.print_format import read_multi_pdf -from frappe.utils.jinja import render_template + +IRS_1099_FORMS_FILE_EXTENSION = ".pdf" def execute(filters=None): - filters = filters if isinstance(filters, _dict) else _dict(filters) + filters = filters if isinstance(filters, frappe._dict) else frappe._dict(filters) if not filters: filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0]) filters.setdefault('company', frappe.db.get_default("company")) - region = frappe.db.get_value("Company", fieldname = ["country"], filters = { "name": filters.company }) + region = frappe.db.get_value("Company", + filters={"name": filters.company}, + fieldname=["country"]) + if region != 'United States': - return [],[] + return [], [] data = [] columns = get_columns() + conditions = "" + if filters.supplier_group: + conditions += "AND s.supplier_group = %s" %frappe.db.escape(filters.get("supplier_group")) + data = frappe.db.sql(""" SELECT s.supplier_group as "supplier_group", @@ -33,20 +43,25 @@ def execute(filters=None): s.tax_id as "tax_id", SUM(gl.debit_in_account_currency) AS "payments" FROM - `tabGL Entry` gl INNER JOIN `tabSupplier` s + `tabGL Entry` gl + INNER JOIN `tabSupplier` s WHERE s.name = gl.party - AND s.irs_1099 = 1 - AND gl.fiscal_year = %(fiscal_year)s - AND gl.party_type = "Supplier" + AND s.irs_1099 = 1 + AND gl.fiscal_year = %(fiscal_year)s + AND gl.party_type = "Supplier" + AND gl.company = %(company)s + {conditions} GROUP BY gl.party - + ORDER BY - gl.party DESC""", {"fiscal_year": filters.fiscal_year, - "supplier_group": filters.supplier_group, - "company": filters.company}, as_dict=True) + gl.party DESC""".format(conditions=conditions), { + "fiscal_year": filters.fiscal_year, + "company": filters.company + }, as_dict=True) + return columns, data @@ -70,14 +85,13 @@ def get_columns(): "fieldname": "tax_id", "label": _("Tax ID"), "fieldtype": "Data", - "width": 120 + "width": 200 }, { - "fieldname": "payments", "label": _("Total Payments"), "fieldtype": "Currency", - "width": 120 + "width": 200 } ] @@ -87,23 +101,33 @@ def irs_1099_print(filters): if not filters: frappe._dict({ "company": frappe.db.get_default("Company"), - "fiscal_year": frappe.db.get_default("fiscal_year")}) + "fiscal_year": frappe.db.get_default("Fiscal Year") + }) else: filters = frappe._dict(json.loads(filters)) + + fiscal_year_doc = get_fiscal_year(fiscal_year=filters.fiscal_year, as_dict=True) + fiscal_year = cstr(fiscal_year_doc.year_start_date.year) + company_address = get_payer_address_html(filters.company) company_tin = frappe.db.get_value("Company", filters.company, "tax_id") + columns, data = execute(filters) template = frappe.get_doc("Print Format", "IRS 1099 Form").html output = PdfFileWriter() + for row in data: + row["fiscal_year"] = fiscal_year row["company"] = filters.company row["company_tin"] = company_tin row["payer_street_address"] = company_address - row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html("Supplier", row.supplier) + row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html( + "Supplier", row.supplier) row["payments"] = fmt_money(row["payments"], precision=0, currency="USD") - frappe._dict(row) pdf = get_pdf(render_template(template, row), output=output if output else None) - frappe.local.response.filename = filters.fiscal_year + " " + filters.company + " IRS 1099 Forms" + + frappe.local.response.filename = "{0} {1} IRS 1099 Forms{2}".format(filters.fiscal_year, + filters.company, IRS_1099_FORMS_FILE_EXTENSION) frappe.local.response.filecontent = read_multi_pdf(output) frappe.local.response.type = "download" @@ -119,36 +143,45 @@ def get_payer_address_html(company): ORDER BY address_type="Postal" DESC, address_type="Billing" DESC LIMIT 1 - """, {"company": company}, as_dict=True) + """, {"company": company}, as_dict=True) + + address_display = "" if address_list: company_address = address_list[0]["name"] - return frappe.get_doc("Address", company_address).get_display() - else: - return "" + address_display = frappe.get_doc("Address", company_address).get_display() + + return address_display def get_street_address_html(party_type, party): address_list = frappe.db.sql(""" SELECT link.parent - FROM `tabDynamic Link` link, `tabAddress` address - WHERE link.parenttype = "Address" - AND link.link_name = %(party)s - ORDER BY address.address_type="Postal" DESC, + FROM + `tabDynamic Link` link, + `tabAddress` address + WHERE + link.parenttype = "Address" + AND link.link_name = %(party)s + ORDER BY + address.address_type="Postal" DESC, address.address_type="Billing" DESC LIMIT 1 - """, {"party": party}, as_dict=True) + """, {"party": party}, as_dict=True) + + street_address = city_state = "" if address_list: supplier_address = address_list[0]["parent"] doc = frappe.get_doc("Address", supplier_address) + if doc.address_line2: - street = doc.address_line1 + "
\n" + doc.address_line2 + "
\n" + street_address = doc.address_line1 + "
\n" + doc.address_line2 + "
\n" else: - street = doc.address_line1 + "
\n" - city = doc.city + ", " if doc.city else "" - city = city + doc.state + " " if doc.state else city - city = city + doc.pincode if doc.pincode else city - city += "
\n" - return street, city - else: - return "", "" + street_address = doc.address_line1 + "
\n" + + city_state = doc.city + ", " if doc.city else "" + city_state = city_state + doc.state + " " if doc.state else city_state + city_state = city_state + doc.pincode if doc.pincode else city_state + city_state += "
\n" + + return street_address, city_state diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index d19caec1929..ff8a677ce41 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -502,7 +502,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( make_delivery_note: function() { frappe.model.open_mapped_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - frm: me.frm + frm: this.frm }) }, diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 37c29befcd7..000d8eb1834 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -319,11 +319,14 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 20) - def test_add_new_item_in_update_child_qty_rate(self): + def test_update_child_adding_new_item(self): so = make_sales_order(item_code= "_Test Item", qty=4) create_dn_against_so(so.name, 4) make_sales_invoice(so.name) + prev_total = so.get("base_total") + prev_total_in_words = so.get("base_in_words") + first_item_of_so = so.get("items")[0] trans_item = json.dumps([ {'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \ @@ -339,7 +342,13 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.status, 'To Deliver and Bill') - def test_remove_item_in_update_child_qty_rate(self): + updated_total = so.get("base_total") + updated_total_in_words = so.get("base_in_words") + + self.assertEqual(updated_total, prev_total+1400) + self.assertNotEqual(updated_total_in_words, prev_total_in_words) + + def test_update_child_removing_item(self): so = make_sales_order(**{ "item_list": [{ "item_code": '_Test Item', @@ -382,7 +391,7 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.status, 'To Deliver and Bill') - def test_update_child_qty_rate(self): + def test_update_child(self): so = make_sales_order(item_code= "_Test Item", qty=4) create_dn_against_so(so.name, 4) make_sales_invoice(so.name) @@ -419,7 +428,7 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.items[0].rate, 200.34669) make_property_setter("Sales Order Item", "rate", "precision", precision, "Currency") - def test_update_child_qty_rate_perm(self): + def test_update_child_perm(self): so = make_sales_order(item_code= "_Test Item", qty=4) user = 'test@example.com' @@ -472,7 +481,7 @@ class TestSalesOrder(unittest.TestCase): workflow.is_active = 0 workflow.save() - def test_update_child_qty_rate_product_bundle(self): + def test_update_child_product_bundle(self): # test Update Items with product bundle if not frappe.db.exists("Item", "_Product Bundle Item"): bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0}) @@ -492,6 +501,20 @@ class TestSalesOrder(unittest.TestCase): so.reload() self.assertEqual(so.packed_items[0].qty, 4) + # test uom and conversion factor change + update_uom_conv_factor = json.dumps([{ + 'item_code': so.get("items")[0].item_code, + 'rate': so.get("items")[0].rate, + 'qty': so.get("items")[0].qty, + 'uom': "_Test UOM 1", + 'conversion_factor': 2, + 'docname': so.get("items")[0].name + }]) + update_child_qty_rate('Sales Order', update_uom_conv_factor, so.name) + + so.reload() + self.assertEqual(so.packed_items[0].qty, 8) + def test_update_child_with_tax_template(self): """ Test Action: Create a SO with one item having its tax account head already in the SO. diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 837dfd5bd30..dd931e179b3 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -25,10 +25,11 @@ class StockLedgerEntry(Document): def validate(self): self.flags.ignore_submit_comment = True - from erpnext.stock.utils import validate_warehouse_company + from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse self.validate_mandatory() self.validate_item() self.validate_batch() + validate_disabled_warehouse(self.warehouse) validate_warehouse_company(self.warehouse, self.company) self.scrub_posting_time() self.validate_and_set_fiscal_year() diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index a804917381f..4919f8b4c05 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -211,7 +211,8 @@ def get_stock_ledger_entries(filters): from `tabItem` {item_conditions}) item where item_code = item.name and company = %(company)s and - posting_date <= %(to_date)s + posting_date <= %(to_date)s and + is_cancelled != 1 {sle_conditions} order by posting_date, posting_time, sle.creation, actual_qty""" #nosec .format(item_conditions=get_item_conditions(filters), diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index da4b529b01e..bef7450b7ef 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ import json -from frappe.utils import flt, cstr, nowdate, nowtime +from frappe.utils import flt, cstr, nowdate, nowtime, get_link_to_form from six import string_types @@ -279,6 +279,10 @@ def is_group_warehouse(warehouse): if frappe.db.get_value("Warehouse", warehouse, "is_group"): frappe.throw(_("Group node warehouse is not allowed to select for transactions")) +def validate_disabled_warehouse(warehouse): + if frappe.db.get_value("Warehouse", warehouse, "disabled"): + frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse))) + def update_included_uom_in_report(columns, result, include_uom, conversion_factors): if not include_uom or not conversion_factors: return diff --git a/requirements.txt b/requirements.txt index 20e43c44948..c49b3f5c707 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ frappe gocardless-pro==1.11.0 googlemaps==3.1.1 pandas==0.24.2 -plaid-python==6.0.0 +plaid-python>=7.0.0 PyGithub==1.44.1 python-stdnum==1.12 Unidecode==1.1.1 WooCommerce==2.1.1 -pycryptodome==3.9.8 \ No newline at end of file +pycryptodome==3.9.8