Merge branch 'develop' into subcontracting
This commit is contained in:
commit
4ff734c25a
@ -448,8 +448,6 @@ class LoanRepayment(AccountsController):
|
||||
"remarks": remarks,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(self.posting_date),
|
||||
"party_type": self.applicant_type if self.repay_from_salary else "",
|
||||
"party": self.applicant if self.repay_from_salary else "",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -42,6 +42,10 @@ class JobCardCancelError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class JobCardOverTransferError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class JobCard(Document):
|
||||
def onload(self):
|
||||
excess_transfer = frappe.db.get_single_value(
|
||||
@ -522,23 +526,50 @@ class JobCard(Document):
|
||||
},
|
||||
)
|
||||
|
||||
def set_transferred_qty_in_job_card(self, ste_doc):
|
||||
def set_transferred_qty_in_job_card_item(self, ste_doc):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
def _validate_over_transfer(row, transferred_qty):
|
||||
"Block over transfer of items if not allowed in settings."
|
||||
required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty")
|
||||
is_excess = flt(transferred_qty) > flt(required_qty)
|
||||
if is_excess:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}"
|
||||
).format(
|
||||
row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card
|
||||
),
|
||||
title=_("Excess Transfer"),
|
||||
exc=JobCardOverTransferError,
|
||||
)
|
||||
|
||||
for row in ste_doc.items:
|
||||
if not row.job_card_item:
|
||||
continue
|
||||
|
||||
qty = frappe.db.sql(
|
||||
""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
|
||||
WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
|
||||
se.purpose = 'Material Transfer for Manufacture'
|
||||
""",
|
||||
(row.job_card_item),
|
||||
)[0][0]
|
||||
sed = frappe.qb.DocType("Stock Entry Detail")
|
||||
se = frappe.qb.DocType("Stock Entry")
|
||||
transferred_qty = (
|
||||
frappe.qb.from_(sed)
|
||||
.join(se)
|
||||
.on(sed.parent == se.name)
|
||||
.select(Sum(sed.qty))
|
||||
.where(
|
||||
(sed.job_card_item == row.job_card_item)
|
||||
& (se.docstatus == 1)
|
||||
& (se.purpose == "Material Transfer for Manufacture")
|
||||
)
|
||||
).run()[0][0]
|
||||
|
||||
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty))
|
||||
allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
|
||||
if not allow_excess:
|
||||
_validate_over_transfer(row, transferred_qty)
|
||||
|
||||
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
|
||||
|
||||
def set_transferred_qty(self, update_status=False):
|
||||
"Set total FG Qty for which RM was transferred."
|
||||
"Set total FG Qty in Job Card for which RM was transferred."
|
||||
if not self.items:
|
||||
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
||||
|
||||
|
@ -10,6 +10,7 @@ from frappe.utils import random_string
|
||||
from frappe.utils.data import add_to_date, now
|
||||
|
||||
from erpnext.manufacturing.doctype.job_card.job_card import (
|
||||
JobCardOverTransferError,
|
||||
OperationMismatchError,
|
||||
OverlapError,
|
||||
make_corrective_job_card,
|
||||
@ -165,6 +166,7 @@ class TestJobCard(FrappeTestCase):
|
||||
# transfer was made for 2 fg qty in first transfer Stock Entry
|
||||
self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
|
||||
|
||||
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
|
||||
def test_job_card_excess_material_transfer(self):
|
||||
"Test transferring more than required RM against Job Card."
|
||||
self.transfer_material_against = "Job Card"
|
||||
@ -207,6 +209,30 @@ class TestJobCard(FrappeTestCase):
|
||||
# JC is Completed with excess transfer
|
||||
self.assertEqual(job_card.status, "Completed")
|
||||
|
||||
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
|
||||
def test_job_card_excess_material_transfer_block(self):
|
||||
|
||||
self.transfer_material_against = "Job Card"
|
||||
self.source_warehouse = "Stores - _TC"
|
||||
|
||||
self.generate_required_stock(self.work_order)
|
||||
|
||||
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
||||
|
||||
# fully transfer both RMs
|
||||
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
|
||||
transfer_entry_1.insert()
|
||||
transfer_entry_1.submit()
|
||||
|
||||
# transfer extra qty of both RM due to previously damaged RM
|
||||
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
|
||||
# deliberately change 'For Quantity'
|
||||
transfer_entry_2.fg_completed_qty = 1
|
||||
transfer_entry_2.items[0].qty = 5
|
||||
transfer_entry_2.items[1].qty = 3
|
||||
transfer_entry_2.insert()
|
||||
self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit)
|
||||
|
||||
def test_job_card_partial_material_transfer(self):
|
||||
"Test partial material transfer against Job Card"
|
||||
self.transfer_material_against = "Job Card"
|
||||
|
@ -359,7 +359,7 @@ erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
|
||||
erpnext.patches.v13_0.update_accounts_in_loan_docs
|
||||
erpnext.patches.v14_0.update_batch_valuation_flag
|
||||
erpnext.patches.v14_0.delete_non_profit_doctypes
|
||||
erpnext.patches.v14_0.update_employee_advance_status
|
||||
erpnext.patches.v13_0.update_employee_advance_status
|
||||
erpnext.patches.v13_0.add_cost_center_in_loans
|
||||
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
|
||||
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
|
||||
|
@ -16,6 +16,7 @@ from frappe.utils import (
|
||||
comma_and,
|
||||
date_diff,
|
||||
flt,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
)
|
||||
|
||||
@ -45,6 +46,7 @@ class PayrollEntry(Document):
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_employee_details()
|
||||
self.validate_payroll_payable_account()
|
||||
if self.validate_attendance:
|
||||
if self.validate_employee_attendance():
|
||||
frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
|
||||
@ -66,6 +68,14 @@ class PayrollEntry(Document):
|
||||
if len(emp_with_sal_slip):
|
||||
frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
|
||||
|
||||
def validate_payroll_payable_account(self):
|
||||
if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Account type cannot be set for payroll payable account {0}, please remove and try again"
|
||||
).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account)))
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
frappe.delete_doc(
|
||||
"Salary Slip",
|
||||
|
@ -789,11 +789,23 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
|
||||
$.each(this.frm.doc['payments'] || [], function(index, data) {
|
||||
if(data.default && payment_status && total_amount_to_pay > 0) {
|
||||
let base_amount = flt(total_amount_to_pay, precision("base_amount", data));
|
||||
let base_amount, amount;
|
||||
|
||||
if (me.frm.doc.party_account_currency == me.frm.doc.currency) {
|
||||
// if customer/supplier currency is same as company currency
|
||||
// total_amount_to_pay is already in customer/supplier currency
|
||||
// so base_amount has to be calculated using total_amount_to_pay
|
||||
base_amount = flt(total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data));
|
||||
amount = flt(total_amount_to_pay, precision("amount", data));
|
||||
} else {
|
||||
base_amount = flt(total_amount_to_pay, precision("base_amount", data));
|
||||
amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data));
|
||||
}
|
||||
|
||||
frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount);
|
||||
let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data));
|
||||
frappe.model.set_value(data.doctype, data.name, "amount", amount);
|
||||
payment_status = false;
|
||||
|
||||
} else if(me.frm.doc.paid_amount) {
|
||||
frappe.model.set_value(data.doctype, data.name, "amount", 0.0);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
||||
'shipping_address': frm.doc.shipping_address || '',
|
||||
'shipping_address_name': frm.doc.shipping_address_name || '',
|
||||
'customer_address': frm.doc.customer_address || '',
|
||||
'company_address': frm.doc.company_address,
|
||||
'supplier_address': frm.doc.supplier_address,
|
||||
'customer': frm.doc.customer,
|
||||
'supplier': frm.doc.supplier,
|
||||
|
@ -23,7 +23,7 @@ form_grid_templates = {"items": "templates/form_grid/material_request_grid.html"
|
||||
|
||||
class MaterialRequest(BuyingController):
|
||||
def get_feed(self):
|
||||
return _("{0}: {1}").format(self.status, self.material_request_type)
|
||||
return
|
||||
|
||||
def check_if_already_pulled(self):
|
||||
pass
|
||||
|
@ -1162,7 +1162,7 @@ class StockEntry(StockController):
|
||||
if self.job_card:
|
||||
job_doc = frappe.get_doc("Job Card", self.job_card)
|
||||
job_doc.set_transferred_qty(update_status=True)
|
||||
job_doc.set_transferred_qty_in_job_card(self)
|
||||
job_doc.set_transferred_qty_in_job_card_item(self)
|
||||
|
||||
if self.work_order:
|
||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||
|
@ -199,7 +199,7 @@ def process_args(args):
|
||||
if not args.get("price_list"):
|
||||
args.price_list = args.get("selling_price_list") or args.get("buying_price_list")
|
||||
|
||||
if args.barcode:
|
||||
if not args.item_code and args.barcode:
|
||||
args.item_code = get_item_code(barcode=args.barcode)
|
||||
elif not args.item_code and args.serial_no:
|
||||
args.item_code = get_item_code(serial_no=args.serial_no)
|
||||
|
@ -252,11 +252,14 @@ def notify_errors(exceptions_list):
|
||||
)
|
||||
|
||||
for exception in exceptions_list:
|
||||
exception = json.loads(exception)
|
||||
error_message = """<div class='small text-muted'>{0}</div><br>""".format(
|
||||
_(exception.get("message"))
|
||||
)
|
||||
content += error_message
|
||||
try:
|
||||
exception = json.loads(exception)
|
||||
error_message = """<div class='small text-muted'>{0}</div><br>""".format(
|
||||
_(exception.get("message"))
|
||||
)
|
||||
content += error_message
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
content += _("Regards,") + "<br>" + _("Administrator")
|
||||
|
||||
|
@ -1,24 +1,29 @@
|
||||
<h4>{{_("Request for Quotation")}}</h4>
|
||||
<p>{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},</p>
|
||||
<p>{{ message }}</p>
|
||||
|
||||
<p>{{_("The Request for Quotation can be accessed by clicking on the following button")}}:</p>
|
||||
<p>
|
||||
<button style="border: 1px solid #15c; padding: 6px; border-radius: 5px; background-color: white;">
|
||||
<a href="{{ rfq_link }}" style="color: #15c; text-decoration:none;" target="_blank">Submit your Quotation</a>
|
||||
</button>
|
||||
</p><br>
|
||||
|
||||
<p>{{_("Regards")}},<br>
|
||||
{{ user_fullname }}</p><br>
|
||||
|
||||
<br>
|
||||
<a
|
||||
href="{{ rfq_link }}"
|
||||
class="btn btn-default btn-sm"
|
||||
target="_blank">
|
||||
{{ _("Submit your Quotation") }}
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
{% if update_password_link %}
|
||||
|
||||
<br>
|
||||
<p>{{_("Please click on the following button to set your new password")}}:</p>
|
||||
<p>
|
||||
<button style="border: 1px solid #15c; padding: 4px; border-radius: 5px; background-color: white;">
|
||||
<a href="{{ update_password_link }}" style="color: #15c; font-size: 12px; text-decoration:none;" target="_blank">{{_("Update Password") }}</a>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<a
|
||||
href="{{ update_password_link }}"
|
||||
class="btn btn-default btn-xs"
|
||||
target="_blank">
|
||||
{{_("Set Password") }}
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
{% endif %}
|
||||
<p>
|
||||
{{_("Regards")}},<br>
|
||||
{{ user_fullname }}
|
||||
</p>
|
||||
|
@ -8,6 +8,7 @@ class TestSearch(unittest.TestCase):
|
||||
# Search for the word "cond", part of the word "conduire" (Lead) in french.
|
||||
def test_contact_search_in_foreign_language(self):
|
||||
try:
|
||||
frappe.local.lang_full_dict = None # reset cached translations
|
||||
frappe.local.lang = "fr"
|
||||
output = filter_dynamic_link_doctypes(
|
||||
"DocType", "cond", "name", 0, 20, {"fieldtype": "HTML", "fieldname": "contact_html"}
|
||||
|
Loading…
x
Reference in New Issue
Block a user