Merge pull request #34161 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Deepesh Garg
2023-02-21 22:45:54 +05:30
committed by GitHub
69 changed files with 956 additions and 480 deletions

View File

@@ -4,7 +4,7 @@
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
@@ -16,6 +16,7 @@ erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev

View File

@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger"];
},
refresh: function(frm) {

View File

@@ -89,7 +89,13 @@ class JournalEntry(AccountsController):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
unlink_ref_doc_from_payment_entries(self)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
)
self.make_gl_entries(1)
self.update_advance_paid()
self.unlink_advance_entry_reference()
@@ -238,21 +244,16 @@ class JournalEntry(AccountsController):
):
processed_assets.append(d.reference_name)
asset = frappe.db.get_value(
"Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
)
asset = frappe.get_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation - depr_value,
)
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
asset.set_status()
def update_inter_company_jv(self):
if (
@@ -341,12 +342,9 @@ class JournalEntry(AccountsController):
else:
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation + depr_value,
)
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
asset.set_status()
def unlink_inter_company_jv(self):
if (

View File

@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);

View File

@@ -239,7 +239,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"label": "Account Currency (From)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -249,7 +249,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_balance",
"fieldtype": "Currency",
"label": "Account Balance",
"label": "Account Balance (From)",
"options": "paid_from_account_currency",
"print_hide": 1,
"read_only": 1
@@ -272,7 +272,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"label": "Account Currency (To)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -282,7 +282,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_balance",
"fieldtype": "Currency",
"label": "Account Balance",
"label": "Account Balance (To)",
"options": "paid_to_account_currency",
"print_hide": 1,
"read_only": 1
@@ -304,7 +304,7 @@
{
"fieldname": "source_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
"label": "Source Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -334,7 +334,7 @@
{
"fieldname": "target_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
"label": "Target Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -633,14 +633,14 @@
"depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "purchase_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"label": "Purchase Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template"
},
{
"depends_on": "eval: doc.party_type == 'Customer'",
"fieldname": "sales_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"label": "Sales Taxes and Charges Template",
"options": "Sales Taxes and Charges Template"
},
{
@@ -733,7 +733,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-12-08 16:25:43.824051",
"modified": "2023-02-14 04:52:30.478523",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -92,7 +92,13 @@ class PaymentEntry(AccountsController):
self.set_status()
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
)
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()

View File

@@ -368,6 +368,7 @@ class PaymentReconciliation(Document):
"exchange_rate": 1,
"cost_center": erpnext.get_default_cost_center(self.company),
reverse_dr_or_cr + "_in_account_currency": flt(row.difference_amount),
reverse_dr_or_cr: flt(row.difference_amount),
}
)

View File

@@ -495,7 +495,7 @@ def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype"""
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
grand_total = flt(ref_doc.rounded_total) - flt(ref_doc.advance_paid)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:

View File

@@ -21,8 +21,24 @@ class POSClosingEntry(StatusUpdater):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
self.validate_duplicate_pos_invoices()
self.validate_pos_invoices()
def validate_duplicate_pos_invoices(self):
pos_occurences = {}
for idx, inv in enumerate(self.pos_transactions, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = []
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
)
if error_list:
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
def validate_pos_invoices(self):
invalid_rows = []
for d in self.pos_transactions:

View File

@@ -18,6 +18,22 @@ class POSInvoiceMergeLog(Document):
def validate(self):
self.validate_customer()
self.validate_pos_invoice_status()
self.validate_duplicate_pos_invoices()
def validate_duplicate_pos_invoices(self):
pos_occurences = {}
for idx, inv in enumerate(self.pos_invoices, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = []
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
)
if error_list:
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
def validate_customer(self):
if self.merge_invoices_based_on == "Customer Group":
@@ -426,6 +442,8 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry:
closing_entry.set_status(update=True, status="Failed")
if type(error_message) == list:
error_message = frappe.json.dumps(error_message)
closing_entry.db_set("error_message", error_message)
raise

View File

@@ -472,7 +472,7 @@
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Rate"
"label": "Free Item Rate"
},
{
"collapsible": 1,
@@ -608,7 +608,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2022-10-13 19:05:35.056304",
"modified": "2023-02-14 04:53:34.887358",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger"];
if(!this.frm.doc.__islocal) {
// show credit_to in print format

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _, throw
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
import erpnext
@@ -1416,6 +1417,8 @@ class PurchaseInvoice(BuyingController):
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Payment Ledger Entry",
"Tax Withheld Vouchers",
)
@@ -1463,19 +1466,16 @@ class PurchaseInvoice(BuyingController):
def update_billing_status_in_pr(self, update_modified=True):
updated_pr = []
po_details = []
pr_details_billed_amt = self.get_pr_details_billed_amt()
for d in self.get("items"):
if d.pr_detail:
billed_amt = frappe.db.sql(
"""select sum(amount) from `tabPurchase Invoice Item`
where pr_detail=%s and docstatus=1""",
d.pr_detail,
)
billed_amt = billed_amt and billed_amt[0][0] or 0
frappe.db.set_value(
"Purchase Receipt Item",
d.pr_detail,
"billed_amt",
billed_amt,
flt(pr_details_billed_amt.get(d.pr_detail)),
update_modified=update_modified,
)
updated_pr.append(d.purchase_receipt)
@@ -1491,6 +1491,24 @@ class PurchaseInvoice(BuyingController):
pr_doc = frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
def get_pr_details_billed_amt(self):
# Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice
pr_details_billed_amt = {}
pr_details = [d.get("pr_detail") for d in self.get("items") if d.get("pr_detail")]
if pr_details:
doctype = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(doctype)
.select(doctype.pr_detail, Sum(doctype.amount))
.where(doctype.pr_detail.isin(pr_details) & doctype.docstatus == 1)
.groupby(doctype.pr_detail)
)
pr_details_billed_amt = frappe._dict(query.run(as_list=1))
return pr_details_billed_amt
def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None

View File

@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry'];
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger"];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
@@ -64,6 +64,25 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) {
this.frm.set_intro(__("Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update."));
this.frm.add_custom_button(__('Repost Accounting Entries'),
() => {
this.frm.call({
doc: this.frm.doc,
method: 'repost_accounting_entries',
freeze: true,
freeze_message: __('Reposting...'),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__('Accounting Entries are reposted'));
me.frm.refresh();
}
}
});
}).removeClass('btn-default').addClass('btn-warning');
}
if (this.frm.doc.is_return) {
this.frm.return_print_format = "Sales Invoice Return";
}

View File

@@ -209,6 +209,7 @@
"is_internal_customer",
"is_discounted",
"remarks",
"repost_required",
"connections_tab"
],
"fields": [
@@ -1038,6 +1039,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "redeem_loyalty_points",
"fieldname": "loyalty_redemption_account",
"fieldtype": "Link",
@@ -1336,6 +1338,7 @@
"options": "fa fa-money"
},
{
"allow_on_submit": 1,
"depends_on": "is_pos",
"fieldname": "cash_bank_account",
"fieldtype": "Link",
@@ -1435,6 +1438,7 @@
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "is_pos",
"fieldname": "account_for_change_amount",
"fieldtype": "Link",
@@ -1483,6 +1487,7 @@
"hide_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "write_off_account",
"fieldtype": "Link",
"hide_days": 1,
@@ -1706,6 +1711,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
@@ -1922,6 +1928,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_internal_customer",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
@@ -1964,6 +1971,7 @@
"label": "Disable Rounded Total"
},
{
"allow_on_submit": 1,
"fieldname": "additional_discount_account",
"fieldtype": "Link",
"label": "Discount Account",
@@ -2114,6 +2122,15 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"no_copy": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -2126,7 +2143,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2023-01-28 19:45:47.538163",
"modified": "2022-11-07 16:02:07.972258",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -11,6 +11,9 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,
validate_loyalty_points,
@@ -100,13 +103,11 @@ class SalesInvoice(SellingController):
self.validate_debit_to_acc()
self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.add_remarks()
self.validate_write_off_account()
self.validate_account_for_change_amount()
self.validate_fixed_asset()
self.set_income_account_for_fixed_assets()
self.validate_item_cost_centers()
self.validate_income_account()
self.check_conversion_rate()
self.validate_accounts()
validate_inter_company_party(
self.doctype, self.customer, self.company, self.inter_company_invoice_reference
@@ -170,6 +171,11 @@ class SalesInvoice(SellingController):
self.reset_default_field_value("set_warehouse", "items", "warehouse")
def validate_accounts(self):
self.validate_write_off_account()
self.validate_account_for_change_amount()
self.validate_income_account()
def validate_fixed_asset(self):
for d in self.get("items"):
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
@@ -368,6 +374,7 @@ class SalesInvoice(SellingController):
self.repost_future_sle_and_gle()
self.db_set("status", "Cancelled")
self.db_set("repost_required", 0)
if (
frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction"
@@ -390,6 +397,8 @@ class SalesInvoice(SellingController):
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Payment Ledger Entry",
)
@@ -514,6 +523,92 @@ class SalesInvoice(SellingController):
def on_update(self):
self.set_paid_amount()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
needs_repost = 0
# Check if any field affecting accounting entry is altered
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if opening entry check updated
if doc_before_update.get("is_opening") != self.is_opening:
needs_repost = 1
if not needs_repost:
# Parent Level Accounts excluding party account
for field in (
"additional_discount_account",
"cash_bank_account",
"account_for_change_amount",
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
):
if doc_before_update.get(field) != self.get(field):
needs_repost = 1
break
# Check for parent accounting dimensions
for dimension in accounting_dimensions:
if doc_before_update.get(dimension) != self.get(dimension):
needs_repost = 1
break
# Check for child tables
if self.check_if_child_table_updated(
"items",
doc_before_update,
("income_account", "expense_account", "discount_account"),
accounting_dimensions,
):
needs_repost = 1
if self.check_if_child_table_updated(
"taxes", doc_before_update, ("account_head",), accounting_dimensions
):
needs_repost = 1
self.validate_accounts()
# validate if deferred revenue is enabled for any item
# Don't allow to update the invoice if deferred revenue is enabled
for item in self.get("items"):
if item.enable_deferred_revenue:
frappe.throw(
_(
"Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission."
).format(item.item_code)
)
self.db_set("repost_required", needs_repost)
def check_if_child_table_updated(
self, child_table, doc_before_update, fields_to_check, accounting_dimensions
):
# Check if any field affecting accounting entry is altered
for index, item in enumerate(self.get(child_table)):
for field in fields_to_check:
if doc_before_update.get(child_table)[index].get(field) != item.get(field):
return True
for dimension in accounting_dimensions:
if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension):
return True
return False
@frappe.whitelist()
def repost_accounting_entries(self):
if self.repost_required:
self.docstatus = 2
self.make_gl_entries_on_cancel()
self.docstatus = 1
self.make_gl_entries()
self.db_set("repost_required", 0)
else:
frappe.throw(_("No updates pending for reposting"))
def set_paid_amount(self):
paid_amount = 0.0
base_paid_amount = 0.0

View File

@@ -2769,6 +2769,31 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
# Update Invoice post submit and then check GL Entries again
si.load_from_db()
si.items[0].income_account = "Service - _TC"
si.additional_discount_account = "_Test Account Sales - _TC"
si.taxes[0].account_head = "TDS Payable - _TC"
si.save()
si.load_from_db()
self.assertTrue(si.repost_required)
si.repost_accounting_entries()
expected_gle = [
["_Test Account Sales - _TC", 22.0, 0.0, nowdate()],
["Debtors - _TC", 88, 0.0, nowdate()],
["Service - _TC", 0.0, 100.0, nowdate()],
["TDS Payable - _TC", 0.0, 10.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
si.load_from_db()
self.assertFalse(si.repost_required)
def test_asset_depreciation_on_sale_with_pro_rata(self):
"""
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
@@ -3326,6 +3351,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
"""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
and is_cancelled = 0
order by posting_date asc, account asc""",
(voucher_no, posting_date),
as_dict=1,

View File

@@ -438,6 +438,7 @@
"label": "Accounting Details"
},
{
"allow_on_submit": 1,
"fieldname": "income_account",
"fieldtype": "Link",
"label": "Income Account",
@@ -450,6 +451,7 @@
"width": "120px"
},
{
"allow_on_submit": 1,
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Expense Account",
@@ -469,6 +471,7 @@
"print_hide": 1
},
{
"allow_on_submit": 1,
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
@@ -800,6 +803,7 @@
"options": "Finance Book"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -822,7 +826,6 @@
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1
},
{
@@ -835,6 +838,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
@@ -886,7 +890,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-12-28 16:17:33.484531",
"modified": "2022-10-17 12:51:44.825398",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -51,6 +51,7 @@
"oldfieldtype": "Data"
},
{
"allow_on_submit": 1,
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
@@ -63,6 +64,7 @@
"search_index": 1
},
{
"allow_on_submit": 1,
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
@@ -216,12 +218,13 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-08-05 20:04:01.726867",
"modified": "2022-10-17 13:08:17.776528",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
"sort_order": "ASC",
"states": []
}

View File

@@ -278,7 +278,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
if cint(tax_details.round_off_tax_amount):
tax_amount = round(tax_amount)
tax_amount = normal_round(tax_amount)
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
@@ -603,3 +603,20 @@ def is_valid_certificate(
valid = True
return valid
def normal_round(number):
"""
Rounds a number to the nearest integer.
:param number: The number to round.
"""
decimal_part = number - int(number)
if decimal_part >= 0.5:
decimal_part = 1
else:
decimal_part = 0
number = int(number) + decimal_part
return number

View File

@@ -250,7 +250,7 @@ frappe.ui.form.on('Asset', {
$.each(depr_entries || [], function(i, v) {
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1]
asset_values.push(last_asset_value - v.value);
asset_values.push(flt(last_asset_value - v.value, precision('gross_purchase_amount')));
});
}

View File

@@ -677,11 +677,15 @@ class Asset(AccountsController):
if self.journal_entry_for_scrap:
status = "Scrapped"
elif self.finance_books:
idx = self.get_default_finance_book_idx() or 0
else:
expected_value_after_useful_life = 0
value_after_depreciation = self.value_after_depreciation
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
value_after_depreciation = self.finance_books[idx].value_after_depreciation
if self.calculate_depreciation:
idx = self.get_default_finance_book_idx() or 0
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
value_after_depreciation = self.finance_books[idx].value_after_depreciation
if flt(value_after_depreciation) <= expected_value_after_useful_life:
status = "Fully Depreciated"
@@ -843,6 +847,7 @@ class Asset(AccountsController):
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.orderby(gle.posting_date)
.orderby(gle.creation)
).run(as_dict=True)
return records

View File

@@ -137,7 +137,7 @@ def make_depreciation_entry(asset_name, date=None):
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
asset.db_set("depr_entry_posting_status", "Successful")
asset.set_status()

View File

@@ -77,6 +77,9 @@ class AssetRepair(AccountsController):
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()
def check_repair_status(self):
if self.repair_status == "Pending":
frappe.throw(_("Please update Repair Status."))

View File

@@ -151,6 +151,7 @@ def prepare_chart_data(data, filters):
filters.filter_based_on,
"Monthly",
company=filters.company,
ignore_fiscal_year=True,
)
for d in period_list:

View File

@@ -21,6 +21,7 @@
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
"show_pay_button",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -140,6 +141,12 @@
"fieldname": "disable_last_purchase_rate",
"fieldtype": "Check",
"label": "Disable Last Purchase Rate"
},
{
"default": "1",
"fieldname": "show_pay_button",
"fieldtype": "Check",
"label": "Show Pay Button in Purchase Order Portal"
}
],
"icon": "fa fa-cog",
@@ -147,7 +154,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-01-09 17:08:28.828173",
"modified": "2023-02-15 14:42:10.200679",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -124,12 +124,11 @@ frappe.ui.form.on("Request for Quotation",{
frappe.urllib.get_full_url(
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
new URLSearchParams({
doctype: frm.doc.doctype,
name: frm.doc.name,
supplier: data.supplier,
print_format: data.print_format || "Standard",
language: data.language || frappe.boot.lang,
letter_head: data.letter_head || frm.doc.letter_head || "",
letterhead: data.letter_head || frm.doc.letter_head || "",
}).toString()
)
);

View File

@@ -3,6 +3,7 @@
import json
from typing import Optional
import frappe
from frappe import _
@@ -388,24 +389,26 @@ def create_rfq_items(sq_doc, supplier, data):
@frappe.whitelist()
def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None):
# permissions get checked in `download_pdf`
if doc := get_rfq_doc(doctype, name, supplier):
download_pdf(
doctype,
name,
print_format,
doc=doc,
language=language,
letter_head=letter_head or None,
)
def get_rfq_doc(doctype, name, supplier):
def get_pdf(
name: str,
supplier: str,
print_format: Optional[str] = None,
language: Optional[str] = None,
letterhead: Optional[str] = None,
):
doc = frappe.get_doc("Request for Quotation", name)
if supplier:
doc = frappe.get_doc(doctype, name)
doc.update_supplier_part_no(supplier)
return doc
# permissions get checked in `download_pdf`
download_pdf(
doc.doctype,
doc.name,
print_format,
doc=doc,
language=language,
letterhead=letterhead or None,
)
@frappe.whitelist()

View File

@@ -8,6 +8,7 @@ from frappe.utils import nowdate
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
create_supplier_quotation,
get_pdf,
make_supplier_quotation_from_rfq,
)
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
@@ -124,6 +125,11 @@ class TestRequestforQuotation(FrappeTestCase):
rfq.status = "Draft"
rfq.submit()
def test_get_pdf(self):
rfq = make_request_for_quotation()
get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
self.assertEqual(frappe.local.response.type, "pdf")
def make_request_for_quotation(**args):
"""

View File

@@ -204,6 +204,12 @@ class AccountsController(TransactionBase):
validate_einvoice_fields(self)
def on_trash(self):
# delete references in 'Repost Payment Ledger'
rpi = frappe.qb.DocType("Repost Payment Ledger Items")
frappe.qb.from_(rpi).delete().where(
(rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name)
).run()
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
ple = frappe.qb.DocType("Payment Ledger Entry")

View File

@@ -409,7 +409,14 @@ class SubcontractingController(StockController):
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
if batch_qty >= qty:
if batch_qty >= qty or (
rm_obj.consumed_qty == 0
and self.backflush_based_on == "BOM"
and len(self.available_materials[key]["batch_no"]) == 1
):
if rm_obj.consumed_qty == 0:
self.__set_consumed_qty(rm_obj, qty)
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
self.available_materials[key]["batch_no"][batch_no] -= qty
return

View File

@@ -26,10 +26,11 @@
}
],
"links": [],
"modified": "2021-02-08 12:51:48.971517",
"modified": "2023-02-10 00:51:44.973957",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead Source",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -58,5 +59,7 @@
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": [],
"translated_doctype": 1
}

View File

@@ -18,10 +18,11 @@
}
],
"links": [],
"modified": "2020-05-20 12:22:01.866472",
"modified": "2023-02-10 01:40:23.713390",
"modified_by": "Administrator",
"module": "CRM",
"name": "Sales Stage",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -40,5 +41,7 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
"states": [],
"track_changes": 1,
"translated_doctype": 1
}

View File

@@ -306,7 +306,6 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
execute:frappe.delete_doc("DocType", "Naming Series")
erpnext.patches.v13_0.job_card_status_on_hold
erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.crm_ux_cleanup
erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
@@ -315,10 +314,9 @@ erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.drop_unused_sle_index_parts
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.update_partial_tds_fields
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
erpnext.patches.v14_0.setup_clear_repost_logs
@@ -326,4 +324,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
erpnext.patches.v14_0.update_entry_type_for_journal_entry
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
erpnext.patches.v14_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v14_0.set_pick_list_status
erpnext.patches.v14_0.set_pick_list_status
# below 2 migration patches should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger

View File

@@ -1,16 +1,17 @@
import frappe
from frappe import _
def execute():
from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type
from erpnext.setup.setup_wizard.operations.install_fixtures import read_lines
frappe.reload_doc("selling", "doctype", "sales_partner_type")
frappe.local.lang = frappe.db.get_default("lang") or "en"
default_sales_partner_type = read_lines("sales_partner_type.txt")
for s in default_sales_partner_type:
insert_sales_partner_type(_(s))
insert_sales_partner_type(s)
# get partner type in existing forms (customized)
# and create a document if not created

View File

@@ -17,10 +17,11 @@ def execute():
for report in reports_to_delete:
if frappe.db.exists("Report", report):
delete_links_from_desktop_icons(report)
delete_auto_email_reports(report)
check_and_delete_linked_reports(report)
frappe.delete_doc("Report", report)
frappe.delete_doc("Report", report, force=True)
def delete_auto_email_reports(report):
@@ -28,3 +29,10 @@ def delete_auto_email_reports(report):
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
def delete_links_from_desktop_icons(report):
"""Check for one or multiple Desktop Icons and delete"""
desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
for desktop_icon in desktop_icons:
frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True)

View File

@@ -408,7 +408,7 @@
"depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)",
"fieldname": "daily_time_to_send",
"fieldtype": "Time",
"label": "Time to send"
"label": "Daily Time to send"
},
{
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
@@ -421,7 +421,7 @@
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
"fieldname": "weekly_time_to_send",
"fieldtype": "Time",
"label": "Time to send"
"label": "Weekly Time to send"
},
{
"fieldname": "column_break_45",
@@ -451,7 +451,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
"modified": "2022-06-23 16:45:06.108499",
"modified": "2023-02-14 04:54:25.819620",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
@@ -497,4 +497,4 @@
"timeline_field": "customer",
"title_field": "project_name",
"track_seen": 1
}
}

View File

@@ -282,21 +282,21 @@
{
"fieldname": "base_total_costing_amount",
"fieldtype": "Currency",
"label": "Total Costing Amount",
"label": "Base Total Costing Amount",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "base_total_billable_amount",
"fieldtype": "Currency",
"label": "Total Billable Amount",
"label": "Base Total Billable Amount",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "base_total_billed_amount",
"fieldtype": "Currency",
"label": "Total Billed Amount",
"label": "Base Total Billed Amount",
"print_hide": 1,
"read_only": 1
},
@@ -311,10 +311,11 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-06-15 22:08:53.930200",
"modified": "2023-02-14 04:55:41.735991",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -388,5 +389,6 @@
],
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"title_field": "title"
}

View File

@@ -143,6 +143,12 @@ var get_payment_mode_account = function(frm, mode_of_payment, callback) {
cur_frm.cscript.account_head = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (doc.docstatus == 1) {
// Should not trigger any changes on change post submit
return;
}
if(!d.charge_type && d.account_head){
frappe.msgprint(__("Please select Charge Type first"));
frappe.model.set_value(cdt, cdn, "account_head", "");

View File

@@ -221,9 +221,9 @@ $.extend(erpnext.utils, {
callback: function(r) {
if (r.message && r.message.length) {
r.message.forEach((dimension) => {
let found = filters.some(el => el.fieldname === dimension['fieldname']);
let existing_filter = filters.filter(el => el.fieldname === dimension['fieldname']);
if (!found) {
if (!existing_filter.length) {
filters.splice(index, 0, {
"fieldname": dimension["fieldname"],
"label": __(dimension["doctype"]),
@@ -232,6 +232,11 @@ $.extend(erpnext.utils, {
return frappe.db.get_link_options(dimension["doctype"], txt);
},
});
} else {
existing_filter[0]['fieldtype'] = "MultiSelectList";
existing_filter[0]['get_data'] = function(txt) {
return frappe.db.get_link_options(dimension["doctype"], txt);
}
}
});
}

View File

@@ -1,123 +1,68 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:industry",
"beta": 0,
"creation": "2012-03-27 14:36:09",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:industry",
"creation": "2012-03-27 14:36:09",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"industry"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "industry",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Industry",
"length": 0,
"no_copy": 0,
"oldfieldname": "industry",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "industry",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Industry",
"oldfieldname": "industry",
"oldfieldtype": "Data",
"reqd": 1,
"unique": 1
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-flag",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Selling",
"name": "Industry Type",
"owner": "Administrator",
],
"icon": "fa fa-flag",
"idx": 1,
"links": [],
"modified": "2023-02-10 03:14:40.735763",
"modified_by": "Administrator",
"module": "Selling",
"name": "Industry Type",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User"
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"translated_doctype": 1
}

View File

@@ -1,94 +1,47 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:sales_partner_type",
"beta": 0,
"creation": "2018-06-11 13:15:57.404716",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"autoname": "field:sales_partner_type",
"creation": "2018-06-11 13:15:57.404716",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_partner_type"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_partner_type",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Sales Partner Type",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "sales_partner_type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Sales Partner Type",
"reqd": 1,
"unique": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-11 13:45:13.554307",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Partner Type",
"name_case": "",
"owner": "Administrator",
],
"links": [],
"modified": "2023-02-10 01:00:20.110800",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Partner Type",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"translated_doctype": 1
}

View File

@@ -216,7 +216,7 @@ def get_sales_order_details(company_list, filters):
)
if filters.get("item_group"):
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_group))
query = query.where(db_so_item.item_group == filters.item_group)
if filters.get("from_date"):
query = query.where(db_so.transaction_date >= filters.from_date)
@@ -225,7 +225,7 @@ def get_sales_order_details(company_list, filters):
query = query.where(db_so.transaction_date <= filters.to_date)
if filters.get("item_code"):
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_code))
query = query.where(db_so_item.item_code == filters.item_code)
if filters.get("customer"):
query = query.where(db_so.customer == filters.customer)

View File

@@ -31,7 +31,7 @@
"icon": "fa fa-bookmark",
"idx": 1,
"links": [],
"modified": "2022-06-28 17:10:26.853753",
"modified": "2023-02-10 01:53:41.319386",
"modified_by": "Administrator",
"module": "Setup",
"name": "Designation",
@@ -58,5 +58,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": []
"states": [],
"translated_doctype": 1
}

View File

@@ -3,13 +3,17 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.desk.notifications import clear_notifications
from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils import cint, create_batch
class TransactionDeletionRecord(Document):
def __init__(self, *args, **kwargs):
super(TransactionDeletionRecord, self).__init__(*args, **kwargs)
self.batch_size = 5000
def validate(self):
frappe.only_for("System Manager")
self.validate_doctypes_to_be_ignored()
@@ -155,8 +159,9 @@ class TransactionDeletionRecord(Document):
"DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options"
)
for table in child_tables:
frappe.db.delete(table, {"parent": ["in", parent_docs_to_be_deleted]})
for batch in create_batch(parent_docs_to_be_deleted, self.batch_size):
for table in child_tables:
frappe.db.delete(table, {"parent": ["in", batch]})
def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
frappe.db.delete(doctype, {company_fieldname: self.company})
@@ -181,13 +186,16 @@ class TransactionDeletionRecord(Document):
frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix))
def delete_version_log(self, doctype, company_fieldname):
frappe.db.sql(
"""delete from `tabVersion` where ref_doctype=%s and docname in
(select name from `tab{0}` where `{1}`=%s)""".format(
doctype, company_fieldname
),
(doctype, self.company),
)
dt = qb.DocType(doctype)
names = qb.from_(dt).select(dt.name).where(dt[company_fieldname] == self.company).run(as_list=1)
names = [x[0] for x in names]
if names:
versions = qb.DocType("Version")
for batch in create_batch(names, self.batch_size):
qb.from_(versions).delete().where(
(versions.ref_doctype == doctype) & (versions.docname.isin(batch))
).run()
def delete_communications(self, doctype, company_fieldname):
reference_docs = frappe.get_all(doctype, filters={company_fieldname: self.company})
@@ -199,7 +207,8 @@ class TransactionDeletionRecord(Document):
)
communication_names = [c.name for c in communications]
frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
for batch in create_batch(communication_names, self.batch_size):
frappe.delete_doc("Communication", batch, ignore_permissions=True)
@frappe.whitelist()

View File

@@ -0,0 +1,31 @@
Accountant
Administrative Assistant
Administrative Officer
Analyst
Associate
Business Analyst
Business Development Manager
Consultant
Chief Executive Officer
Chief Financial Officer
Chief Operating Officer
Chief Technology Officer
Customer Service Representative
Designer
Engineer
Executive Assistant
Finance Manager
HR Manager
Head of Marketing and Sales
Manager
Managing Director
Marketing Manager
Marketing Specialist
President
Product Manager
Project Manager
Researcher
Sales Representative
Secretary
Software Developer
Vice President

View File

@@ -1,57 +0,0 @@
from frappe import _
def get_industry_types():
return [
_("Accounting"),
_("Advertising"),
_("Aerospace"),
_("Agriculture"),
_("Airline"),
_("Apparel & Accessories"),
_("Automotive"),
_("Banking"),
_("Biotechnology"),
_("Broadcasting"),
_("Brokerage"),
_("Chemical"),
_("Computer"),
_("Consulting"),
_("Consumer Products"),
_("Cosmetics"),
_("Defense"),
_("Department Stores"),
_("Education"),
_("Electronics"),
_("Energy"),
_("Entertainment & Leisure"),
_("Executive Search"),
_("Financial Services"),
_("Food, Beverage & Tobacco"),
_("Grocery"),
_("Health Care"),
_("Internet Publishing"),
_("Investment Banking"),
_("Legal"),
_("Manufacturing"),
_("Motion Picture & Video"),
_("Music"),
_("Newspaper Publishers"),
_("Online Auctions"),
_("Pension Funds"),
_("Pharmaceuticals"),
_("Private Equity"),
_("Publishing"),
_("Real Estate"),
_("Retail & Wholesale"),
_("Securities & Commodity Exchanges"),
_("Service"),
_("Soap & Detergent"),
_("Software"),
_("Sports"),
_("Technology"),
_("Telecommunications"),
_("Television"),
_("Transportation"),
_("Venture Capital"),
]

View File

@@ -0,0 +1,51 @@
Accounting
Advertising
Aerospace
Agriculture
Airline
Apparel & Accessories
Automotive
Banking
Biotechnology
Broadcasting
Brokerage
Chemical
Computer
Consulting
Consumer Products
Cosmetics
Defense
Department Stores
Education
Electronics
Energy
Entertainment & Leisure
Executive Search
Financial Services
Food, Beverage & Tobacco
Grocery
Health Care
Internet Publishing
Investment Banking
Legal
Manufacturing
Motion Picture & Video
Music
Newspaper Publishers
Online Auctions
Pension Funds
Pharmaceuticals
Private Equity
Publishing
Real Estate
Retail & Wholesale
Securities & Commodity Exchanges
Service
Soap & Detergent
Software
Sports
Technology
Telecommunications
Television
Transportation
Venture Capital

View File

@@ -0,0 +1,10 @@
Existing Customer
Reference
Advertisement
Cold Calling
Exhibition
Supplier Reference
Mass Mailing
Customer's Vendor
Campaign
Walk In

View File

@@ -0,0 +1,7 @@
Channel Partner
Distributor
Dealer
Agent
Retailer
Implementation Partner
Reseller

View File

@@ -0,0 +1,8 @@
Prospecting
Qualification
Needs Analysis
Value Proposition
Identifying Decision Makers
Perception Analysis
Proposal/Price Quote
Negotiation/Review

View File

@@ -4,6 +4,7 @@
import json
import os
from pathlib import Path
import frappe
from frappe import _
@@ -16,28 +17,10 @@ from frappe.utils import cstr, getdate
from erpnext.accounts.doctype.account.account import RootNotEditable
from erpnext.regional.address_template.setup import set_up_address_templates
default_lead_sources = [
"Existing Customer",
"Reference",
"Advertisement",
"Cold Calling",
"Exhibition",
"Supplier Reference",
"Mass Mailing",
"Customer's Vendor",
"Campaign",
"Walk In",
]
default_sales_partner_type = [
"Channel Partner",
"Distributor",
"Dealer",
"Agent",
"Retailer",
"Implementation Partner",
"Reseller",
]
def read_lines(filename: str) -> list[str]:
"""Return a list of lines from a file in the data directory."""
return (Path(__file__).parent.parent / "data" / filename).read_text().splitlines()
def install(country=None):
@@ -85,7 +68,11 @@ def install(country=None):
# Stock Entry Type
{"doctype": "Stock Entry Type", "name": "Material Issue", "purpose": "Material Issue"},
{"doctype": "Stock Entry Type", "name": "Material Receipt", "purpose": "Material Receipt"},
{"doctype": "Stock Entry Type", "name": "Material Transfer", "purpose": "Material Transfer"},
{
"doctype": "Stock Entry Type",
"name": "Material Transfer",
"purpose": "Material Transfer",
},
{"doctype": "Stock Entry Type", "name": "Manufacture", "purpose": "Manufacture"},
{"doctype": "Stock Entry Type", "name": "Repack", "purpose": "Repack"},
{
@@ -103,22 +90,6 @@ def install(country=None):
"name": "Material Consumption for Manufacture",
"purpose": "Material Consumption for Manufacture",
},
# Designation
{"doctype": "Designation", "designation_name": _("CEO")},
{"doctype": "Designation", "designation_name": _("Manager")},
{"doctype": "Designation", "designation_name": _("Analyst")},
{"doctype": "Designation", "designation_name": _("Engineer")},
{"doctype": "Designation", "designation_name": _("Accountant")},
{"doctype": "Designation", "designation_name": _("Secretary")},
{"doctype": "Designation", "designation_name": _("Associate")},
{"doctype": "Designation", "designation_name": _("Administrative Officer")},
{"doctype": "Designation", "designation_name": _("Business Development Manager")},
{"doctype": "Designation", "designation_name": _("HR Manager")},
{"doctype": "Designation", "designation_name": _("Project Manager")},
{"doctype": "Designation", "designation_name": _("Head of Marketing and Sales")},
{"doctype": "Designation", "designation_name": _("Software Developer")},
{"doctype": "Designation", "designation_name": _("Designer")},
{"doctype": "Designation", "designation_name": _("Researcher")},
# territory: with two default territories, one for home country and one named Rest of the World
{
"doctype": "Territory",
@@ -291,28 +262,18 @@ def install(country=None):
{"doctype": "Market Segment", "market_segment": _("Lower Income")},
{"doctype": "Market Segment", "market_segment": _("Middle Income")},
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
# Sales Stages
{"doctype": "Sales Stage", "stage_name": _("Prospecting")},
{"doctype": "Sales Stage", "stage_name": _("Qualification")},
{"doctype": "Sales Stage", "stage_name": _("Needs Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Value Proposition")},
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
# Warehouse Type
{"doctype": "Warehouse Type", "name": "Transit"},
]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types
records += [{"doctype": "Industry Type", "industry": d} for d in get_industry_types()]
# records += [{"doctype":"Operation", "operation": d} for d in get_operations()]
records += [{"doctype": "Lead Source", "source_name": _(d)} for d in default_lead_sources]
records += [
{"doctype": "Sales Partner Type", "sales_partner_type": _(d)} for d in default_sales_partner_type
]
for doctype, title_field, filename in (
("Designation", "designation_name", "designation.txt"),
("Sales Stage", "stage_name", "sales_stage.txt"),
("Industry Type", "industry", "industry_type.txt"),
("Lead Source", "source_name", "lead_source.txt"),
("Sales Partner Type", "sales_partner_type", "sales_partner_type.txt"),
):
records += [{"doctype": doctype, title_field: title} for title in read_lines(filename)]
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
response = frappe.read_file(
@@ -397,7 +358,8 @@ def add_uom_data():
frappe.get_doc({"doctype": "UOM Category", "category_name": _(d.get("category"))}).db_insert()
if not frappe.db.exists(
"UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}
"UOM Conversion Factor",
{"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))},
):
frappe.get_doc(
{
@@ -535,7 +497,8 @@ def create_bank_account(args):
company_name = args.get("company_name")
bank_account_group = frappe.db.get_value(
"Account", {"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name}
"Account",
{"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name},
)
if bank_account_group:
bank_account = frappe.get_doc(

View File

@@ -97,12 +97,12 @@ frappe.ui.form.on("Delivery Note", {
}
if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) {
let internal = me.frm.doc.is_internal_customer;
let internal = frm.doc.is_internal_customer;
if (internal) {
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Receipt" :
let button_label = (frm.doc.company === frm.doc.represents_company) ? "Internal Purchase Receipt" :
"Inter Company Purchase Receipt";
me.frm.add_custom_button(button_label, function() {
frm.add_custom_button(__(button_label), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt',
frm: frm,

View File

@@ -521,6 +521,7 @@
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
"label": "Delivery Note Item",
"oldfieldname": "delivery_note_details",
"oldfieldtype": "Table",
"options": "Delivery Note Item",
@@ -666,6 +667,7 @@
{
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges"
@@ -1401,7 +1403,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2022-12-12 18:38:53.067799",
"modified": "2023-02-14 04:45:44.179670",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -14,7 +14,7 @@ frappe.listview_settings['Delivery Note'] = {
return [__("Completed"), "green", "per_billed,=,100"];
}
},
onload: function (listview) {
onload: function (doclist) {
const action = () => {
const selected_docs = doclist.get_checked_items();
const docnames = doclist.get_checked_items(true);
@@ -56,14 +56,14 @@ frappe.listview_settings['Delivery Note'] = {
// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
listview.page.add_action_item(__('Create Delivery Trip'), action);
doclist.page.add_action_item(__('Create Delivery Trip'), action);
listview.page.add_action_item(__("Sales Invoice"), ()=>{
erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Sales Invoice");
doclist.page.add_action_item(__("Sales Invoice"), ()=>{
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
});
listview.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Packing Slip");
doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
});
}
};

View File

@@ -706,7 +706,7 @@
"depends_on": "enable_deferred_expense",
"fieldname": "no_of_months_exp",
"fieldtype": "Int",
"label": "No of Months"
"label": "No of Months (Expense)"
},
{
"collapsible": 1,
@@ -911,7 +911,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
"modified": "2022-09-13 04:08:17.431731",
"modified": "2023-02-14 04:48:26.343620",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -108,10 +108,13 @@ frappe.ui.form.on('Material Request', {
() => frm.events.create_pick_list(frm), __('Create'));
}
if (frm.doc.material_request_type === "Material Transfer") {
if (frm.doc.material_request_type === 'Material Transfer') {
add_create_pick_list_button();
frm.add_custom_button(__("Transfer Material"),
frm.add_custom_button(__('Material Transfer'),
() => frm.events.make_stock_entry(frm), __('Create'));
frm.add_custom_button(__('Material Transfer (In Transit)'),
() => frm.events.make_in_transit_stock_entry(frm), __('Create'));
}
if (frm.doc.material_request_type === "Material Issue") {
@@ -333,6 +336,46 @@ frappe.ui.form.on('Material Request', {
});
},
make_in_transit_stock_entry(frm) {
frappe.prompt(
[
{
label: __('In Transit Warehouse'),
fieldname: 'in_transit_warehouse',
fieldtype: 'Link',
options: 'Warehouse',
reqd: 1,
get_query: () => {
return{
filters: {
'company': frm.doc.company,
'is_group': 0,
'warehouse_type': 'Transit'
}
}
}
}
],
(values) => {
frappe.call({
method: "erpnext.stock.doctype.material_request.material_request.make_in_transit_stock_entry",
args: {
source_name: frm.doc.name,
in_transit_warehouse: values.in_transit_warehouse
},
callback: function(r) {
if (r.message) {
let doc = frappe.model.sync(r.message);
frappe.set_route('Form', doc[0].doctype, doc[0].name);
}
}
})
},
__('In Transit Transfer'),
__('Create Stock Entry')
)
},
create_pick_list: (frm) => {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",

View File

@@ -719,3 +719,14 @@ def create_pick_list(source_name, target_doc=None):
doc.set_item_locations()
return doc
@frappe.whitelist()
def make_in_transit_stock_entry(source_name, in_transit_warehouse):
ste_doc = make_stock_entry(source_name)
ste_doc.add_to_transit = 1
for row in ste_doc.items:
row.t_warehouse = in_transit_warehouse
return ste_doc

View File

@@ -11,6 +11,7 @@ from frappe.utils import flt, today
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.material_request.material_request import (
make_in_transit_stock_entry,
make_purchase_order,
make_stock_entry,
make_supplier_quotation,
@@ -56,6 +57,22 @@ class TestMaterialRequest(FrappeTestCase):
self.assertEqual(se.doctype, "Stock Entry")
self.assertEqual(len(se.get("items")), len(mr.get("items")))
def test_in_transit_make_stock_entry(self):
mr = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.material_request_type = "Material Transfer"
mr.submit()
in_transit_warehouse = get_in_transit_warehouse(mr.company)
se = make_in_transit_stock_entry(mr.name, in_transit_warehouse)
self.assertEqual(se.doctype, "Stock Entry")
for row in se.get("items"):
self.assertEqual(row.t_warehouse, in_transit_warehouse)
def _insert_stock_entry(self, qty1, qty2, warehouse=None):
se = frappe.get_doc(
{
@@ -742,6 +759,36 @@ class TestMaterialRequest(FrappeTestCase):
self.assertEqual(existing_requested_qty, current_requested_qty)
def get_in_transit_warehouse(company):
if not frappe.db.exists("Warehouse Type", "Transit"):
frappe.get_doc(
{
"doctype": "Warehouse Type",
"name": "Transit",
}
).insert()
in_transit_warehouse = frappe.db.exists(
"Warehouse", {"warehouse_type": "Transit", "company": company}
)
if not in_transit_warehouse:
in_transit_warehouse = (
frappe.get_doc(
{
"doctype": "Warehouse",
"warehouse_name": "Transit",
"warehouse_type": "Transit",
"company": company,
}
)
.insert()
.name
)
return in_transit_warehouse
def make_material_request(**args):
args = frappe._dict(args)
mr = frappe.new_doc("Material Request")

View File

@@ -887,18 +887,10 @@ def update_billing_percentage(pr_doc, update_modified=True):
# Update Billing % based on pending accepted qty
total_amount, total_billed_amount = 0, 0
for item in pr_doc.items:
return_data = frappe.db.get_list(
"Purchase Receipt",
fields=["sum(abs(`tabPurchase Receipt Item`.qty)) as qty"],
filters=[
["Purchase Receipt", "docstatus", "=", 1],
["Purchase Receipt", "is_return", "=", 1],
["Purchase Receipt Item", "purchase_receipt_item", "=", item.name],
],
)
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
returned_qty = return_data[0].qty if return_data else 0
for item in pr_doc.items:
returned_qty = flt(item_wise_returned_qty.get(item.name))
returned_amount = flt(returned_qty) * flt(item.rate)
pending_amount = flt(item.amount) - returned_amount
total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
@@ -915,6 +907,27 @@ def update_billing_percentage(pr_doc, update_modified=True):
pr_doc.notify_update()
def get_item_wise_returned_qty(pr_doc):
items = [d.name for d in pr_doc.items]
return frappe._dict(
frappe.get_all(
"Purchase Receipt",
fields=[
"`tabPurchase Receipt Item`.purchase_receipt_item",
"sum(abs(`tabPurchase Receipt Item`.qty)) as qty",
],
filters=[
["Purchase Receipt", "docstatus", "=", 1],
["Purchase Receipt", "is_return", "=", 1],
["Purchase Receipt Item", "purchase_receipt_item", "in", items],
],
group_by="`tabPurchase Receipt Item`.purchase_receipt_item",
as_list=1,
)
)
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from erpnext.accounts.party import get_payment_terms_template

View File

@@ -859,7 +859,8 @@
"label": "Purchase Receipt Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"collapsible": 1,
@@ -974,7 +975,8 @@
"label": "Purchase Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "product_bundle",
@@ -1010,7 +1012,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-11-02 12:49:28.746701",
"modified": "2023-01-18 15:48:58.114923",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -2,7 +2,22 @@
// For license information, please see license.txt
frappe.ui.form.on('Stock Reposting Settings', {
// refresh: function(frm) {
refresh: function(frm) {
frm.trigger('convert_to_item_based_reposting');
},
// }
convert_to_item_based_reposting: function(frm) {
frm.add_custom_button(__('Convert to Item Based Reposting'), function() {
frm.call({
method: 'convert_to_item_wh_reposting',
frezz: true,
doc: frm.doc,
callback: function(r) {
if (!r.exc) {
frm.reload_doc();
}
}
})
})
}
});

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours
@@ -24,3 +26,62 @@ class StockRepostingSettings(Document):
if diff < 10:
self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True))
@frappe.whitelist()
def convert_to_item_wh_reposting(self):
"""Convert Transaction reposting to Item Warehouse based reposting if Item Based Reposting has enabled."""
reposting_data = get_reposting_entries()
vouchers = [d.voucher_no for d in reposting_data]
item_warehouses = {}
for ledger in get_stock_ledgers(vouchers):
key = (ledger.item_code, ledger.warehouse)
if key not in item_warehouses:
item_warehouses[key] = ledger.posting_date
elif frappe.utils.getdate(item_warehouses.get(key)) > frappe.utils.getdate(ledger.posting_date):
item_warehouses[key] = ledger.posting_date
for key, posting_date in item_warehouses.items():
item_code, warehouse = key
create_repost_item_valuation(item_code, warehouse, posting_date)
for row in reposting_data:
frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped")
self.db_set("item_based_reposting", 1)
frappe.msgprint(_("Item Warehouse based reposting has been enabled."))
def get_reposting_entries():
return frappe.get_all(
"Repost Item Valuation",
fields=["voucher_no", "name"],
filters={"status": ("in", ["Queued", "In Progress"]), "docstatus": 1, "based_on": "Transaction"},
)
def get_stock_ledgers(vouchers):
return frappe.get_all(
"Stock Ledger Entry",
fields=["item_code", "warehouse", "posting_date"],
filters={"voucher_no": ("in", vouchers)},
)
def create_repost_item_valuation(item_code, warehouse, posting_date):
frappe.get_doc(
{
"doctype": "Repost Item Valuation",
"company": frappe.get_cached_value("Warehouse", warehouse, "company"),
"posting_date": posting_date,
"based_on": "Item and Warehouse",
"posting_time": "00:00:01",
"item_code": item_code,
"warehouse": warehouse,
"allow_negative_stock": True,
"status": "Queued",
}
).submit()

View File

@@ -306,7 +306,7 @@ def get_stock_ledger_entries(filters, items):
query = query.where(sle.item_code.isin(items))
for field in ["voucher_no", "batch_no", "project", "company"]:
if filters.get(field):
if filters.get(field) and field not in inventory_dimension_fields:
query = query.where(sle[field] == filters.get(field))
query = apply_warehouse_filter(query, sle, filters)

View File

@@ -121,7 +121,7 @@ def get_reserved_qty(item_code, warehouse):
and parenttype='Sales Order'
and item_code != parent_item
and exists (select * from `tabSales Order` so
where name = dnpi_in.parent and docstatus = 1 and status != 'Closed')
where name = dnpi_in.parent and docstatus = 1 and status not in ('On Hold', 'Closed'))
) dnpi)
union
(select stock_qty as dnpi_qty, qty as so_item_qty,
@@ -131,7 +131,7 @@ def get_reserved_qty(item_code, warehouse):
and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)
and exists(select * from `tabSales Order` so
where so.name = so_item.parent and so.docstatus = 1
and so.status != 'Closed'))
and so.status not in ('On Hold', 'Closed')))
) tab
where
so_item_qty >= so_item_delivered_qty

View File

@@ -2,6 +2,7 @@
# See license.txt
import copy
from collections import defaultdict
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -186,6 +187,40 @@ class TestSubcontractingOrder(FrappeTestCase):
)
self.assertEqual(len(ste.items), len(rm_items))
def test_make_rm_stock_entry_for_batch_items_with_less_transfer(self):
set_backflush_based_on("BOM")
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 4",
"qty": 5,
"rate": 100,
"fg_item": "Subcontracted Item SA4",
"fg_item_qty": 5,
}
]
sco = get_subcontracting_order(service_items=service_items)
rm_items = get_rm_items(sco.supplied_items)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
itemwise_transfer_qty = defaultdict(int)
for item in rm_items:
item["qty"] -= 1
itemwise_transfer_qty[item["item_code"]] += item["qty"]
ste = make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
scr = make_subcontracting_receipt(sco.name)
for row in scr.supplied_items:
self.assertEqual(row.consumed_qty, itemwise_transfer_qty.get(row.rm_item_code) + 1)
def test_update_reserved_qty_for_subcontracting(self):
# Create RM Material Receipt
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)

View File

@@ -51,13 +51,31 @@ frappe.ui.form.on('Subcontracting Receipt', {
}
}));
frm.set_query("expense_account", "items", function () {
frm.set_query('expense_account', 'items', function () {
return {
query: "erpnext.controllers.queries.get_expense_account",
query: 'erpnext.controllers.queries.get_expense_account',
filters: { 'company': frm.doc.company }
};
});
frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
var row = locals[cdt][cdn];
return {
filters: {
item: row.item_code
}
}
});
let batch_no_field = frm.get_docfield("items", "batch_no");
if (batch_no_field) {
batch_no_field.get_route_options_for_new_doc = function(row) {
return {
"item": row.doc.item_code
}
};
}
frappe.db.get_single_value('Buying Settings', 'backflush_raw_materials_of_subcontract_based_on').then(val => {
if (val == 'Material Transferred for Subcontract') {
frm.fields_dict['supplied_items'].grid.grid_rows.forEach((grid_row) => {
@@ -73,7 +91,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
refresh: (frm) => {
if (frm.doc.docstatus > 0) {
frm.add_custom_button(__("Stock Ledger"), function () {
frm.add_custom_button(__('Stock Ledger'), function () {
frappe.route_options = {
voucher_no: frm.doc.name,
from_date: frm.doc.posting_date,
@@ -81,8 +99,8 @@ frappe.ui.form.on('Subcontracting Receipt', {
company: frm.doc.company,
show_cancelled_entries: frm.doc.docstatus === 2
};
frappe.set_route("query-report", "Stock Ledger");
}, __("View"));
frappe.set_route('query-report', 'Stock Ledger');
}, __('View'));
frm.add_custom_button(__('Accounting Ledger'), function () {
frappe.route_options = {
@@ -90,11 +108,11 @@ frappe.ui.form.on('Subcontracting Receipt', {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)",
group_by: 'Group by Voucher (Consolidated)',
show_cancelled_entries: frm.doc.docstatus === 2
};
frappe.set_route("query-report", "General Ledger");
}, __("View"));
frappe.set_route('query-report', 'General Ledger');
}, __('View'));
}
if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) {
@@ -111,25 +129,25 @@ frappe.ui.form.on('Subcontracting Receipt', {
frm.add_custom_button(__('Subcontracting Order'), function () {
if (!frm.doc.supplier) {
frappe.throw({
title: __("Mandatory"),
message: __("Please Select a Supplier")
title: __('Mandatory'),
message: __('Please Select a Supplier')
});
}
erpnext.utils.map_current_doc({
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt',
source_doctype: "Subcontracting Order",
source_doctype: 'Subcontracting Order',
target: frm,
setters: {
supplier: frm.doc.supplier,
},
get_query_filters: {
docstatus: 1,
per_received: ["<", 100],
per_received: ['<', 100],
company: frm.doc.company
}
});
}, __("Get Items From"));
}, __('Get Items From'));
}
},

View File

@@ -34,16 +34,18 @@
</a>
</ul>
</div>
<div class="form-column col-sm-6">
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
</a>
</p>
{% if show_pay_button %}
<div class="form-column col-sm-6">
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
</a>
</p>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -55,6 +55,7 @@ def get_context(context):
)
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
context.show_pay_button = frappe.db.get_single_value("Buying Settings", "show_pay_button")
context.show_make_pi_button = False
if context.doc.get("supplier"):
# show Make Purchase Invoice button based on permission