Merge branch 'develop' into fix-reserve-qty

This commit is contained in:
Devin Slauenwhite 2022-06-22 14:06:35 -04:00 committed by GitHub
commit 199b8dd0e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 635 additions and 211 deletions

View File

@ -165,17 +165,6 @@ class PurchaseInvoice(BuyingController):
super(PurchaseInvoice, self).set_missing_values(for_validate) super(PurchaseInvoice, self).set_missing_values(for_validate)
def check_conversion_rate(self):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self): def validate_credit_to_acc(self):
if not self.credit_to: if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company) self.credit_to = get_party_account("Supplier", self.supplier, self.company)

View File

@ -114,6 +114,7 @@ class SalesInvoice(SellingController):
self.set_income_account_for_fixed_assets() self.set_income_account_for_fixed_assets()
self.validate_item_cost_centers() self.validate_item_cost_centers()
self.validate_income_account() self.validate_income_account()
self.check_conversion_rate()
validate_inter_company_party( validate_inter_company_party(
self.doctype, self.customer, self.company, self.inter_company_invoice_reference self.doctype, self.customer, self.company, self.inter_company_invoice_reference

View File

@ -1583,6 +1583,17 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gle) self.assertTrue(gle)
def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=1,
do_not_save=1,
)
self.assertRaises(frappe.ValidationError, si.save)
def test_invalid_currency(self): def test_invalid_currency(self):
# Customer currency = USD # Customer currency = USD

View File

@ -59,6 +59,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
for (let option of status){ for (let option of status){
options.push({ options.push({
"value": option, "value": option,
"label": __(option),
"description": "" "description": ""
}) })
} }

View File

@ -1848,6 +1848,17 @@ class AccountsController(TransactionBase):
jv.save() jv.save()
jv.submit() jv.submit()
def check_conversion_rate(self):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):

View File

@ -166,7 +166,7 @@ class StockController(AccountsController):
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision), "debit": -1 * flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, },

View File

@ -445,6 +445,7 @@ class BOM(WebsiteGenerator):
and self.is_active and self.is_active
): ):
frappe.db.set(self, "is_default", 1) frappe.db.set(self, "is_default", 1)
frappe.db.set_value("Item", self.item, "default_bom", self.name)
else: else:
frappe.db.set(self, "is_default", 0) frappe.db.set(self, "is_default", 0)
item = frappe.get_doc("Item", self.item) item = frappe.get_doc("Item", self.item)

View File

@ -559,6 +559,42 @@ class TestBOM(FrappeTestCase):
bom.submit() bom.submit()
self.assertEqual(bom.items[0].rate, 42) self.assertEqual(bom.items[0].rate, 42)
def test_set_default_bom_for_item_having_single_bom(self):
from erpnext.stock.doctype.item.test_item import make_item
fg_item = make_item(properties={"is_stock_item": 1})
bom_item = make_item(properties={"is_stock_item": 1})
# Step 1: Create BOM
bom = frappe.new_doc("BOM")
bom.item = fg_item.item_code
bom.quantity = 1
bom.append(
"items",
{
"item_code": bom_item.item_code,
"qty": 1,
"uom": bom_item.stock_uom,
"stock_uom": bom_item.stock_uom,
"rate": 100.0,
},
)
bom.save()
bom.submit()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
# Step 2: Uncheck is_active field
bom.is_active = 0
bom.save()
bom.reload()
self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom"))
# Step 3: Check is_active field
bom.is_active = 1
bom.save()
bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
def get_default_bom(item_code="_Test FG Item 2"): def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@ -7,11 +7,11 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"current_bom",
"new_bom",
"column_break_3",
"update_type", "update_type",
"status", "status",
"column_break_3",
"current_bom",
"new_bom",
"error_log", "error_log",
"progress_section", "progress_section",
"current_level", "current_level",
@ -37,6 +37,7 @@
"options": "BOM" "options": "BOM"
}, },
{ {
"depends_on": "eval:doc.update_type === \"Replace BOM\"",
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
@ -87,6 +88,7 @@
"options": "BOM Update Batch" "options": "BOM Update Batch"
}, },
{ {
"depends_on": "eval:doc.status !== \"Completed\"",
"fieldname": "current_level", "fieldname": "current_level",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Current Level" "label": "Current Level"
@ -96,7 +98,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-06-06 15:15:23.883251", "modified": "2022-06-20 15:43:55.696388",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Update Log", "name": "BOM Update Log",

View File

@ -6,6 +6,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import Now
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import ( from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
@ -22,6 +24,17 @@ class BOMMissingError(frappe.ValidationError):
class BOMUpdateLog(Document): class BOMUpdateLog(Document):
@staticmethod
def clear_old_logs(days=None):
days = days or 90
table = DocType("BOM Update Log")
frappe.db.delete(
table,
filters=(
(table.modified < (Now() - Interval(days=days))) & (table.update_type == "Update Cost")
),
)
def validate(self): def validate(self):
if self.update_type == "Replace BOM": if self.update_type == "Replace BOM":
self.validate_boms_are_specified() self.validate_boms_are_specified()
@ -77,7 +90,11 @@ class BOMUpdateLog(Document):
now=frappe.flags.in_test, now=frappe.flags.in_test,
) )
else: else:
process_boms_cost_level_wise(self) frappe.enqueue(
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
update_doc=self,
now=frappe.flags.in_test,
)
def run_replace_bom_job( def run_replace_bom_job(
@ -112,28 +129,31 @@ def process_boms_cost_level_wise(
current_boms = {} current_boms = {}
values = {} values = {}
if update_doc.status == "Queued": try:
# First level yet to process. On Submit. if update_doc.status == "Queued":
current_level = 0 # First level yet to process. On Submit.
current_boms = get_leaf_boms() current_level = 0
values = { current_boms = get_leaf_boms()
"processed_boms": json.dumps({}), values = {
"status": "In Progress", "processed_boms": json.dumps({}),
"current_level": current_level, "status": "In Progress",
} "current_level": current_level,
else: }
# Resume next level. via Cron Job. else:
if not parent_boms: # Resume next level. via Cron Job.
return if not parent_boms:
return
current_level = cint(update_doc.current_level) + 1 current_level = cint(update_doc.current_level) + 1
# Process the next level BOMs. Stage parents as current BOMs. # Process the next level BOMs. Stage parents as current BOMs.
current_boms = parent_boms.copy() current_boms = parent_boms.copy()
values = {"current_level": current_level} values = {"current_level": current_level}
set_values_in_log(update_doc.name, values, commit=True) set_values_in_log(update_doc.name, values, commit=True)
queue_bom_cost_jobs(current_boms, update_doc, current_level) queue_bom_cost_jobs(current_boms, update_doc, current_level)
except Exception:
handle_exception(update_doc)
def queue_bom_cost_jobs( def queue_bom_cost_jobs(
@ -199,16 +219,22 @@ def resume_bom_cost_update_jobs():
current_boms, processed_boms = get_processed_current_boms(log, bom_batches) current_boms, processed_boms = get_processed_current_boms(log, bom_batches)
parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms) parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms)
# Unset processed BOMs if log is complete, it is used for next level BOMs # Unset processed BOMs (it is used for next level BOMs) & change status if log is complete
status = "Completed" if not parent_boms else "In Progress"
processed_boms = json.dumps([] if not parent_boms else processed_boms)
set_values_in_log( set_values_in_log(
log.name, log.name,
values={ values={
"processed_boms": json.dumps([] if not parent_boms else processed_boms), "processed_boms": processed_boms,
"status": "Completed" if not parent_boms else "In Progress", "status": status,
}, },
commit=True, commit=True,
) )
# clear progress section
if status == "Completed":
frappe.db.delete("BOM Update Batch", {"parent": log.name})
if parent_boms: # there is a next level to process if parent_boms: # there is a next level to process
process_boms_cost_level_wise( process_boms_cost_level_wise(
update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms

View File

@ -1,6 +1,6 @@
frappe.listview_settings['BOM Update Log'] = { frappe.listview_settings['BOM Update Log'] = {
add_fields: ["status"], add_fields: ["status"],
get_indicator: function(doc) { get_indicator: (doc) => {
let status_map = { let status_map = {
"Queued": "orange", "Queued": "orange",
"In Progress": "blue", "In Progress": "blue",
@ -9,5 +9,22 @@ frappe.listview_settings['BOM Update Log'] = {
}; };
return [__(doc.status), status_map[doc.status], "status,=," + doc.status]; return [__(doc.status), status_map[doc.status], "status,=," + doc.status];
} },
onload: () => {
if (!frappe.model.can_write("Log Settings")) {
return;
}
let sidebar_entry = $(
'<ul class="list-unstyled sidebar-menu log-retention-note"></ul>'
).appendTo(cur_list.page.sidebar);
let message = __("Note: Automatic log deletion only applies to logs of type <i>Update Cost</i>");
$(`<hr><div class='text-muted'>${message}</div>`).appendTo(sidebar_entry);
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
});
},
}; };

View File

@ -1,6 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import copy
import frappe import frappe
from frappe.tests.utils import FrappeTestCase, change_settings, timeout from frappe.tests.utils import FrappeTestCase, change_settings, timeout
from frappe.utils import add_days, add_months, cint, flt, now, today from frappe.utils import add_days, add_months, cint, flt, now, today
@ -19,6 +21,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
) )
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
@ -28,6 +31,7 @@ class TestWorkOrder(FrappeTestCase):
def setUp(self): def setUp(self):
self.warehouse = "_Test Warehouse 2 - _TC" self.warehouse = "_Test Warehouse 2 - _TC"
self.item = "_Test Item" self.item = "_Test Item"
prepare_data_for_backflush_based_on_materials_transferred()
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
@ -527,6 +531,8 @@ class TestWorkOrder(FrappeTestCase):
work_order.cancel() work_order.cancel()
def test_work_order_with_non_transfer_item(self): def test_work_order_with_non_transfer_item(self):
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0} items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0}
for item, allow_transfer in items.items(): for item, allow_transfer in items.items():
make_item(item, {"include_item_in_manufacturing": allow_transfer}) make_item(item, {"include_item_in_manufacturing": allow_transfer})
@ -1071,7 +1077,7 @@ class TestWorkOrder(FrappeTestCase):
sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100)) sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
for row in sm.get("items"): for row in sm.get("items"):
if row.get("item_code") == "_Test Item": if row.get("item_code") == "_Test Item":
row.qty = 110 row.qty = 120
sm.submit() sm.submit()
cancel_stock_entry.append(sm.name) cancel_stock_entry.append(sm.name)
@ -1079,21 +1085,21 @@ class TestWorkOrder(FrappeTestCase):
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90)) s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
for row in s.get("items"): for row in s.get("items"):
if row.get("item_code") == "_Test Item": if row.get("item_code") == "_Test Item":
self.assertEqual(row.get("qty"), 100) self.assertEqual(row.get("qty"), 108)
s.submit() s.submit()
cancel_stock_entry.append(s.name) cancel_stock_entry.append(s.name)
s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
for row in s1.get("items"): for row in s1.get("items"):
if row.get("item_code") == "_Test Item": if row.get("item_code") == "_Test Item":
self.assertEqual(row.get("qty"), 5) self.assertEqual(row.get("qty"), 6)
s1.submit() s1.submit()
cancel_stock_entry.append(s1.name) cancel_stock_entry.append(s1.name)
s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
for row in s2.get("items"): for row in s2.get("items"):
if row.get("item_code") == "_Test Item": if row.get("item_code") == "_Test Item":
self.assertEqual(row.get("qty"), 5) self.assertEqual(row.get("qty"), 6)
cancel_stock_entry.reverse() cancel_stock_entry.reverse()
for ste in cancel_stock_entry: for ste in cancel_stock_entry:
@ -1203,6 +1209,269 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(work_order.required_items[0].transferred_qty, 1) self.assertEqual(work_order.required_items[0].transferred_qty, 1)
self.assertEqual(work_order.required_items[1].transferred_qty, 2) self.assertEqual(work_order.required_items[1].transferred_qty, 2)
def test_backflushed_batch_raw_materials_based_on_transferred(self):
frappe.db.set_value(
"Manufacturing Settings",
None,
"backflush_raw_materials_based_on",
"Material Transferred for Manufacture",
)
batch_item = "Test Batch MCC Keyboard"
fg_item = "Test FG Item with Batch Raw Materials"
ste_doc = test_stock_entry.make_stock_entry(
item_code=batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
)
ste_doc.append(
"items",
{
"item_code": batch_item,
"item_name": batch_item,
"description": batch_item,
"basic_rate": 100,
"t_warehouse": "Stores - _TC",
"qty": 2,
"uom": "Nos",
"stock_uom": "Nos",
"conversion_factor": 1,
},
)
# Inward raw materials in Stores warehouse
ste_doc.insert()
ste_doc.submit()
batch_list = [row.batch_no for row in ste_doc.items]
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
transferred_ste_doc = frappe.get_doc(
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
)
transferred_ste_doc.items[0].qty = 2
transferred_ste_doc.items[0].batch_no = batch_list[0]
new_row = copy.deepcopy(transferred_ste_doc.items[0])
new_row.name = ""
new_row.batch_no = batch_list[1]
# Transferred two batches from Stores to WIP Warehouse
transferred_ste_doc.append("items", new_row)
transferred_ste_doc.submit()
# First Manufacture stock entry
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
# Batch no should be same as transferred Batch no
self.assertEqual(manufacture_ste_doc1.items[0].batch_no, batch_list[0])
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
manufacture_ste_doc1.submit()
# Second Manufacture stock entry
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
# Batch no should be same as transferred Batch no
self.assertEqual(manufacture_ste_doc2.items[0].batch_no, batch_list[0])
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
self.assertEqual(manufacture_ste_doc2.items[1].batch_no, batch_list[1])
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
def test_backflushed_serial_no_raw_materials_based_on_transferred(self):
frappe.db.set_value(
"Manufacturing Settings",
None,
"backflush_raw_materials_based_on",
"Material Transferred for Manufacture",
)
sn_item = "Test Serial No BTT Headphone"
fg_item = "Test FG Item with Serial No Raw Materials"
ste_doc = test_stock_entry.make_stock_entry(
item_code=sn_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
)
# Inward raw materials in Stores warehouse
ste_doc.submit()
serial_nos_list = sorted(get_serial_nos(ste_doc.items[0].serial_no))
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
transferred_ste_doc = frappe.get_doc(
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
)
transferred_ste_doc.items[0].serial_no = "\n".join(serial_nos_list)
transferred_ste_doc.submit()
# First Manufacture stock entry
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
# Serial nos should be same as transferred Serial nos
self.assertEqual(get_serial_nos(manufacture_ste_doc1.items[0].serial_no), serial_nos_list[0:1])
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
manufacture_ste_doc1.submit()
# Second Manufacture stock entry
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
# Serial nos should be same as transferred Serial nos
self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:3])
self.assertEqual(manufacture_ste_doc2.items[0].qty, 2)
def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self):
frappe.db.set_value(
"Manufacturing Settings",
None,
"backflush_raw_materials_based_on",
"Material Transferred for Manufacture",
)
sn_batch_item = "Test Batch Serial No WebCam"
fg_item = "Test FG Item with Serial & Batch No Raw Materials"
ste_doc = test_stock_entry.make_stock_entry(
item_code=sn_batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
)
ste_doc.append(
"items",
{
"item_code": sn_batch_item,
"item_name": sn_batch_item,
"description": sn_batch_item,
"basic_rate": 100,
"t_warehouse": "Stores - _TC",
"qty": 2,
"uom": "Nos",
"stock_uom": "Nos",
"conversion_factor": 1,
},
)
# Inward raw materials in Stores warehouse
ste_doc.insert()
ste_doc.submit()
batch_dict = {row.batch_no: get_serial_nos(row.serial_no) for row in ste_doc.items}
batches = list(batch_dict.keys())
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
transferred_ste_doc = frappe.get_doc(
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
)
transferred_ste_doc.items[0].qty = 2
transferred_ste_doc.items[0].batch_no = batches[0]
transferred_ste_doc.items[0].serial_no = "\n".join(batch_dict.get(batches[0]))
new_row = copy.deepcopy(transferred_ste_doc.items[0])
new_row.name = ""
new_row.batch_no = batches[1]
new_row.serial_no = "\n".join(batch_dict.get(batches[1]))
# Transferred two batches from Stores to WIP Warehouse
transferred_ste_doc.append("items", new_row)
transferred_ste_doc.submit()
# First Manufacture stock entry
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
batch_no = manufacture_ste_doc1.items[0].batch_no
self.assertEqual(
get_serial_nos(manufacture_ste_doc1.items[0].serial_no)[0], batch_dict.get(batch_no)[0]
)
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
manufacture_ste_doc1.submit()
# Second Manufacture stock entry
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
batch_no = manufacture_ste_doc2.items[0].batch_no
self.assertEqual(
get_serial_nos(manufacture_ste_doc2.items[0].serial_no)[0], batch_dict.get(batch_no)[1]
)
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
batch_no = manufacture_ste_doc2.items[1].batch_no
self.assertEqual(
get_serial_nos(manufacture_ste_doc2.items[1].serial_no)[0], batch_dict.get(batch_no)[0]
)
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
def prepare_data_for_backflush_based_on_materials_transferred():
batch_item_doc = make_item(
"Test Batch MCC Keyboard",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TBMK.#####",
"valuation_rate": 100,
"stock_uom": "Nos",
},
)
item = make_item(
"Test FG Item with Batch Raw Materials",
{
"is_stock_item": 1,
},
)
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[batch_item_doc.name])
sn_item_doc = make_item(
"Test Serial No BTT Headphone",
{
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "TSBH.#####",
"valuation_rate": 100,
"stock_uom": "Nos",
},
)
item = make_item(
"Test FG Item with Serial No Raw Materials",
{
"is_stock_item": 1,
},
)
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_item_doc.name])
sn_batch_item_doc = make_item(
"Test Batch Serial No WebCam",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TBSW.#####",
"has_serial_no": 1,
"serial_no_series": "TBSWC.#####",
"valuation_rate": 100,
"stock_uom": "Nos",
},
)
item = make_item(
"Test FG Item with Serial & Batch No Raw Materials",
{
"is_stock_item": 1,
},
)
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
def update_job_card(job_card, jc_qty=None): def update_job_card(job_card, jc_qty=None):
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name") employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")

View File

@ -1,6 +1,6 @@
import frappe import frappe
from frappe import qb from frappe import qb
from frappe.query_builder import Case from frappe.query_builder import Case, CustomFunction
from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull from frappe.query_builder.functions import IfNull
@ -87,6 +87,7 @@ def execute():
gl = qb.DocType("GL Entry") gl = qb.DocType("GL Entry")
account = qb.DocType("Account") account = qb.DocType("Account")
ifelse = CustomFunction("IF", ["condition", "then", "else"])
gl_entries = ( gl_entries = (
qb.from_(gl) qb.from_(gl)
@ -96,8 +97,12 @@ def execute():
gl.star, gl.star,
ConstantColumn(1).as_("docstatus"), ConstantColumn(1).as_("docstatus"),
account.account_type.as_("account_type"), account.account_type.as_("account_type"),
IfNull(gl.against_voucher_type, gl.voucher_type).as_("against_voucher_type"), IfNull(
IfNull(gl.against_voucher, gl.voucher_no).as_("against_voucher_no"), ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
).as_("against_voucher_type"),
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
"against_voucher_no"
),
# convert debit/credit to amount # convert debit/credit to amount
Case() Case()
.when(account.account_type == "Receivable", gl.debit - gl.credit) .when(account.account_type == "Receivable", gl.debit - gl.credit)

View File

@ -375,6 +375,12 @@ def create_internal_customer(
if not allowed_to_interact_with: if not allowed_to_interact_with:
allowed_to_interact_with = represents_company allowed_to_interact_with = represents_company
exisiting_representative = frappe.db.get_value(
"Customer", {"represents_company": represents_company}
)
if exisiting_representative:
return exisiting_representative
if not frappe.db.exists("Customer", customer_name): if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc( customer = frappe.get_doc(
{ {

View File

@ -55,6 +55,7 @@ frappe.query_reports["Sales Order Analysis"] = {
for (let option of status){ for (let option of status){
options.push({ options.push({
"value": option, "value": option,
"label": __(option),
"description": "" "description": ""
}) })
} }

View File

@ -1064,6 +1064,33 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.items[0].rate, rate) self.assertEqual(dn.items[0].rate, rate)
def test_internal_transfer_precision_gle(self):
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
item = make_item(properties={"valuation_method": "Moving Average"}).name
company = "_Test Company with perpetual inventory"
warehouse = "Stores - TCP1"
target = "Finished Goods - TCP1"
customer = create_internal_customer(represents_company=company)
# average rate = 128.015
rates = [101.45, 150.46, 138.25, 121.9]
for rate in rates:
make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate)
dn = create_delivery_note(
item_code=item,
company=company,
customer=customer,
qty=4,
warehouse=warehouse,
target_warehouse=target,
)
self.assertFalse(
frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
)
def test_reserve_qty_on_sales_return(self): def test_reserve_qty_on_sales_return(self):
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
self.reserved_qty_check() self.reserved_qty_check()

View File

@ -596,21 +596,6 @@ class StockEntry(StockController):
title=_("Insufficient Stock"), title=_("Insufficient Stock"),
) )
def set_serial_nos(self, work_order):
previous_se = frappe.db.get_value(
"Stock Entry",
{"work_order": work_order, "purpose": "Material Transfer for Manufacture"},
"name",
)
for d in self.get("items"):
transferred_serial_no = frappe.db.get_value(
"Stock Entry Detail", {"parent": previous_se, "item_code": d.item_code}, "serial_no"
)
if transferred_serial_no:
d.serial_no = transferred_serial_no
@frappe.whitelist() @frappe.whitelist()
def get_stock_and_rate(self): def get_stock_and_rate(self):
""" """
@ -1321,7 +1306,7 @@ class StockEntry(StockController):
and not self.pro_doc.skip_transfer and not self.pro_doc.skip_transfer
and self.flags.backflush_based_on == "Material Transferred for Manufacture" and self.flags.backflush_based_on == "Material Transferred for Manufacture"
): ):
self.get_transfered_raw_materials() self.add_transfered_raw_materials_in_items()
elif ( elif (
self.work_order self.work_order
@ -1365,7 +1350,6 @@ class StockEntry(StockController):
# fetch the serial_no of the first stock entry for the second stock entry # fetch the serial_no of the first stock entry for the second stock entry
if self.work_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
self.set_serial_nos(self.work_order)
work_order = frappe.get_doc("Work Order", self.work_order) work_order = frappe.get_doc("Work Order", self.work_order)
add_additional_cost(self, work_order) add_additional_cost(self, work_order)
@ -1655,119 +1639,78 @@ class StockEntry(StockController):
} }
) )
def get_transfered_raw_materials(self): def add_transfered_raw_materials_in_items(self) -> None:
transferred_materials = frappe.db.sql( available_materials = get_available_materials(self.work_order)
"""
select wo_data = frappe.db.get_value(
item_name, original_item, item_code, sum(qty) as qty, sed.t_warehouse as warehouse, "Work Order",
description, stock_uom, expense_account, cost_center
from `tabStock Entry` se,`tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse
""",
self.work_order, self.work_order,
["qty", "produced_qty", "material_transferred_for_manufacturing as trans_qty"],
as_dict=1, as_dict=1,
) )
materials_already_backflushed = frappe.db.sql( for key, row in available_materials.items():
""" remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
select if remaining_qty_to_produce <= 0:
item_code, sed.s_warehouse as warehouse, sum(qty) as qty continue
from
`tabStock Entry` se, `tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1
and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
group by sed.item_code, sed.s_warehouse
""",
self.work_order,
as_dict=1,
)
backflushed_materials = {} qty = (flt(row.qty) * flt(self.fg_completed_qty)) / remaining_qty_to_produce
for d in materials_already_backflushed:
backflushed_materials.setdefault(d.item_code, []).append({d.warehouse: d.qty})
po_qty = frappe.db.sql(
"""select qty, produced_qty, material_transferred_for_manufacturing from
`tabWork Order` where name=%s""",
self.work_order,
as_dict=1,
)[0]
manufacturing_qty = flt(po_qty.qty) or 1
produced_qty = flt(po_qty.produced_qty)
trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
for item in transferred_materials:
qty = item.qty
item_code = item.original_item or item.item_code
req_items = frappe.get_all(
"Work Order Item",
filters={"parent": self.work_order, "item_code": item_code},
fields=["required_qty", "consumed_qty"],
)
req_qty = flt(req_items[0].required_qty) if req_items else flt(4)
req_qty_each = flt(req_qty / manufacturing_qty)
consumed_qty = flt(req_items[0].consumed_qty) if req_items else 0
if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
if qty >= req_qty:
qty = (req_qty / trans_qty) * flt(self.fg_completed_qty)
else:
qty = qty - consumed_qty
if self.purpose == "Manufacture":
# If Material Consumption is booked, must pull only remaining components to finish product
if consumed_qty != 0:
remaining_qty = consumed_qty - (produced_qty * req_qty_each)
exhaust_qty = req_qty_each * produced_qty
if remaining_qty > exhaust_qty:
if (remaining_qty / (req_qty_each * flt(self.fg_completed_qty))) >= 1:
qty = 0
else:
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else:
if self.flags.backflush_based_on == "Material Transferred for Manufacture":
qty = (item.qty / trans_qty) * flt(self.fg_completed_qty)
else:
qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code):
precision = frappe.get_precision("Stock Entry Detail", "qty")
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse) > 0:
if qty > req_qty:
qty = (
(flt(qty, precision) - flt(d.get(item.warehouse), precision))
/ (flt(trans_qty, precision) - flt(produced_qty, precision))
) * flt(self.fg_completed_qty)
d[item.warehouse] -= qty
item = row.item_details
if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")): if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")):
qty = frappe.utils.ceil(qty) qty = frappe.utils.ceil(qty)
if qty > 0: if row.batch_details:
self.add_to_stock_entry_detail( for batch_no, batch_qty in row.batch_details.items():
{ if qty <= 0 or batch_qty <= 0:
item.item_code: { continue
"from_warehouse": item.warehouse,
"to_warehouse": "", if batch_qty > qty:
"qty": qty, batch_qty = qty
"item_name": item.item_name,
"description": item.description, item.batch_no = batch_no
"stock_uom": item.stock_uom, self.update_item_in_stock_entry_detail(row, item, batch_qty)
"expense_account": item.expense_account,
"cost_center": item.buying_cost_center, row.batch_details[batch_no] -= batch_qty
"original_item": item.original_item, qty -= batch_qty
} else:
} self.update_item_in_stock_entry_detail(row, item, qty)
)
def update_item_in_stock_entry_detail(self, row, item, qty) -> None:
ste_item_details = {
"from_warehouse": item.warehouse,
"to_warehouse": "",
"qty": qty,
"item_name": item.item_name,
"batch_no": item.batch_no,
"description": item.description,
"stock_uom": item.stock_uom,
"expense_account": item.expense_account,
"cost_center": item.buying_cost_center,
"original_item": item.original_item,
}
if row.serial_nos:
serial_nos = row.serial_nos
if item.batch_no:
serial_nos = self.get_serial_nos_based_on_transferred_batch(item.batch_no, row.serial_nos)
serial_nos = serial_nos[0 : cint(qty)]
ste_item_details["serial_no"] = "\n".join(serial_nos)
# remove consumed serial nos from list
for sn in serial_nos:
row.serial_nos.remove(sn)
self.add_to_stock_entry_detail({item.item_code: ste_item_details})
@staticmethod
def get_serial_nos_based_on_transferred_batch(batch_no, serial_nos) -> list:
serial_nos = frappe.get_all(
"Serial No", filters={"batch_no": batch_no, "name": ("in", serial_nos)}, order_by="creation"
)
return [d.name for d in serial_nos]
def get_pending_raw_materials(self, backflush_based_on=None): def get_pending_raw_materials(self, backflush_based_on=None):
""" """
@ -2528,3 +2471,81 @@ def get_supplied_items(purchase_order):
) )
return supplied_item_details return supplied_item_details
def get_available_materials(work_order) -> dict:
data = get_stock_entry_data(work_order)
available_materials = {}
for row in data:
key = (row.item_code, row.warehouse)
if row.purpose != "Material Transfer for Manufacture":
key = (row.item_code, row.s_warehouse)
if key not in available_materials:
available_materials.setdefault(
key,
frappe._dict(
{"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []}
),
)
item_data = available_materials[key]
if row.purpose == "Material Transfer for Manufacture":
item_data.qty += row.qty
if row.batch_no:
item_data.batch_details[row.batch_no] += row.qty
if row.serial_no:
item_data.serial_nos.extend(get_serial_nos(row.serial_no))
item_data.serial_nos.sort()
else:
# Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture'
item_data.qty -= row.qty
if row.batch_no:
item_data.batch_details[row.batch_no] -= row.qty
if row.serial_no:
for serial_no in get_serial_nos(row.serial_no):
item_data.serial_nos.remove(serial_no)
return available_materials
def get_stock_entry_data(work_order):
stock_entry = frappe.qb.DocType("Stock Entry")
stock_entry_detail = frappe.qb.DocType("Stock Entry Detail")
return (
frappe.qb.from_(stock_entry)
.from_(stock_entry_detail)
.select(
stock_entry_detail.item_name,
stock_entry_detail.original_item,
stock_entry_detail.item_code,
stock_entry_detail.qty,
(stock_entry_detail.t_warehouse).as_("warehouse"),
(stock_entry_detail.s_warehouse).as_("s_warehouse"),
stock_entry_detail.description,
stock_entry_detail.stock_uom,
stock_entry_detail.expense_account,
stock_entry_detail.cost_center,
stock_entry_detail.batch_no,
stock_entry_detail.serial_no,
stock_entry.purpose,
)
.where(
(stock_entry.name == stock_entry_detail.parent)
& (stock_entry.work_order == work_order)
& (stock_entry.docstatus == 1)
& (stock_entry_detail.s_warehouse.isnotnull())
& (
stock_entry.purpose.isin(
["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
)
)
)
.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
).run(as_dict=1)

View File

@ -44,7 +44,7 @@ Accessable Value,Доступная стоимость,
Account,Аккаунт, Account,Аккаунт,
Account Number,Номер аккаунта, Account Number,Номер аккаунта,
Account Number {0} already used in account {1},"Номер счета {0}, уже использованный в учетной записи {1}", Account Number {0} already used in account {1},"Номер счета {0}, уже использованный в учетной записи {1}",
Account Pay Only,Счет Оплатить только, Account Pay Only,Только оплатить счет,
Account Type,Тип учетной записи, Account Type,Тип учетной записи,
Account Type for {0} must be {1},Тип счета для {0} должен быть {1}, Account Type for {0} must be {1},Тип счета для {0} должен быть {1},
"Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'","Баланс счета в Кредите, запрещена установка 'Баланс должен быть' как 'Дебет'", "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'","Баланс счета в Кредите, запрещена установка 'Баланс должен быть' как 'Дебет'",
@ -117,7 +117,7 @@ Add Item,Добавить продукт,
Add Items,Добавить продукты, Add Items,Добавить продукты,
Add Leads,Добавить лид, Add Leads,Добавить лид,
Add Multiple Tasks,Добавить несколько задач, Add Multiple Tasks,Добавить несколько задач,
Add Row,Добавить ряд, Add Row,Добавить строку,
Add Sales Partners,Добавить партнеров по продажам, Add Sales Partners,Добавить партнеров по продажам,
Add Serial No,Добавить серийный номер, Add Serial No,Добавить серийный номер,
Add Students,Добавить студентов, Add Students,Добавить студентов,
@ -692,7 +692,7 @@ Created {0} scorecards for {1} between: ,Созданы {0} оценочные
Creating Company and Importing Chart of Accounts,Создание компании и импорт плана счетов, Creating Company and Importing Chart of Accounts,Создание компании и импорт плана счетов,
Creating Fees,Создание сборов, Creating Fees,Создание сборов,
Creating Payment Entries......,Создание платежных записей......, Creating Payment Entries......,Создание платежных записей......,
Creating Salary Slips...,Создание зарплатных листков..., Creating Salary Slips...,Создание зарплатных ведомостей...,
Creating student groups,Создание групп студентов, Creating student groups,Создание групп студентов,
Creating {0} Invoice,Создание {0} счета, Creating {0} Invoice,Создание {0} счета,
Credit,Кредит, Credit,Кредит,
@ -995,7 +995,7 @@ Expenses,Расходы,
Expenses Included In Asset Valuation,"Расходы, включенные в оценку активов", Expenses Included In Asset Valuation,"Расходы, включенные в оценку активов",
Expenses Included In Valuation,"Затрат, включаемых в оценке", Expenses Included In Valuation,"Затрат, включаемых в оценке",
Expired Batches,Просроченные партии, Expired Batches,Просроченные партии,
Expires On,Годен до, Expires On,Актуален до,
Expiring On,Срок действия, Expiring On,Срок действия,
Expiry (In Days),Срок действия (в днях), Expiry (In Days),Срок действия (в днях),
Explore,Обзор, Explore,Обзор,
@ -1411,7 +1411,7 @@ Lab Test UOM,Лабораторная проверка UOM,
Lab Tests and Vital Signs,Лабораторные тесты и жизненные знаки, Lab Tests and Vital Signs,Лабораторные тесты и жизненные знаки,
Lab result datetime cannot be before testing datetime,Лабораторный результат datetime не может быть до тестирования даты и времени, Lab result datetime cannot be before testing datetime,Лабораторный результат datetime не может быть до тестирования даты и времени,
Lab testing datetime cannot be before collection datetime,Лабораторное тестирование datetime не может быть до даты сбора данных, Lab testing datetime cannot be before collection datetime,Лабораторное тестирование datetime не может быть до даты сбора данных,
Label,Ярлык, Label,Метка,
Laboratory,Лаборатория, Laboratory,Лаборатория,
Language Name,Название языка, Language Name,Название языка,
Large,Большой, Large,Большой,
@ -2874,7 +2874,7 @@ Supplier Id,Id поставщика,
Supplier Invoice Date cannot be greater than Posting Date,"Дата Поставщик Счет не может быть больше, чем Дата публикации", Supplier Invoice Date cannot be greater than Posting Date,"Дата Поставщик Счет не может быть больше, чем Дата публикации",
Supplier Invoice No,Поставщик Счет №, Supplier Invoice No,Поставщик Счет №,
Supplier Invoice No exists in Purchase Invoice {0},Номер счета поставщика отсутствует в счете на покупку {0}, Supplier Invoice No exists in Purchase Invoice {0},Номер счета поставщика отсутствует в счете на покупку {0},
Supplier Name,наименование поставщика, Supplier Name,Наименование поставщика,
Supplier Part No,Деталь поставщика №, Supplier Part No,Деталь поставщика №,
Supplier Quotation,Предложение поставщика, Supplier Quotation,Предложение поставщика,
Supplier Scorecard,Оценочная карта поставщика, Supplier Scorecard,Оценочная карта поставщика,
@ -3091,7 +3091,7 @@ Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total,
Total Payments,Всего платежей, Total Payments,Всего платежей,
Total Present,Итого Текущая, Total Present,Итого Текущая,
Total Qty,Общее количество, Total Qty,Общее количество,
Total Quantity,Общая численность, Total Quantity,Общее количество,
Total Revenue,Общий доход, Total Revenue,Общий доход,
Total Student,Всего учеников, Total Student,Всего учеников,
Total Target,Всего целей, Total Target,Всего целей,
@ -3498,7 +3498,7 @@ Postal,Почтовый,
Postal Code,Почтовый индекс, Postal Code,Почтовый индекс,
Previous,Предыдущая, Previous,Предыдущая,
Provider,Поставщик, Provider,Поставщик,
Read Only,Только чтения, Read Only,Только чтение,
Recipient,Сторона-реципиент, Recipient,Сторона-реципиент,
Reviews,Отзывы, Reviews,Отзывы,
Sender,Отправитель, Sender,Отправитель,
@ -3879,7 +3879,7 @@ On Lead Creation,Создание лида,
On Supplier Creation,Создание поставщика, On Supplier Creation,Создание поставщика,
On Customer Creation,Создание клиента, On Customer Creation,Создание клиента,
Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx, Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx,
Only expired allocation can be cancelled,Только истекшее распределение может быть отменено, Only expired allocation can be cancelled,Отменить можно только просроченное распределение,
Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия,
Open,Открыт, Open,Открыт,
Open Contact,Открытый контакт, Open Contact,Открытый контакт,
@ -4046,7 +4046,7 @@ Server Error,Ошибка сервера,
Service Level Agreement has been changed to {0}.,Соглашение об уровне обслуживания изменено на {0}., Service Level Agreement has been changed to {0}.,Соглашение об уровне обслуживания изменено на {0}.,
Service Level Agreement was reset.,Соглашение об уровне обслуживания было сброшено., Service Level Agreement was reset.,Соглашение об уровне обслуживания было сброшено.,
Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Соглашение об уровне обслуживания с типом объекта {0} и объектом {1} уже существует., Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Соглашение об уровне обслуживания с типом объекта {0} и объектом {1} уже существует.,
Set,Задать, Set,Комплект,
Set Meta Tags,Установить метатеги, Set Meta Tags,Установить метатеги,
Set {0} in company {1},Установить {0} в компании {1}, Set {0} in company {1},Установить {0} в компании {1},
Setup,Настройки, Setup,Настройки,
@ -4059,7 +4059,7 @@ Show Stock Ageing Data,Показать данные о старении зап
Show Warehouse-wise Stock,Показать складской запас, Show Warehouse-wise Stock,Показать складской запас,
Size,Размер, Size,Размер,
Something went wrong while evaluating the quiz.,Что-то пошло не так при оценке теста., Something went wrong while evaluating the quiz.,Что-то пошло не так при оценке теста.,
Sr,Sr, Sr,,
Start,Начать, Start,Начать,
Start Date cannot be before the current date,Дата начала не может быть раньше текущей даты, Start Date cannot be before the current date,Дата начала не может быть раньше текущей даты,
Start Time,Время начала, Start Time,Время начала,
@ -4513,7 +4513,7 @@ Mandatory For Profit and Loss Account,Обязательно для счета
Accounting Period,Период учета, Accounting Period,Период учета,
Period Name,Название периода, Period Name,Название периода,
Closed Documents,Закрытые документы, Closed Documents,Закрытые документы,
Accounts Settings,Настройки аккаунта, Accounts Settings,Настройка счетов,
Settings for Accounts,Настройки для счетов, Settings for Accounts,Настройки для счетов,
Make Accounting Entry For Every Stock Movement,Создавать бухгалтерские проводки при каждом перемещении запасов, Make Accounting Entry For Every Stock Movement,Создавать бухгалтерские проводки при каждом перемещении запасов,
Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,"Пользователи с этой ролью могут замороживать счета, а также создавать / изменять бухгалтерские проводки замороженных счетов", Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,"Пользователи с этой ролью могут замороживать счета, а также создавать / изменять бухгалтерские проводки замороженных счетов",
@ -5084,8 +5084,8 @@ Allow Zero Valuation Rate,Разрешить нулевую оценку,
Item Tax Rate,Ставка налогов на продукт, Item Tax Rate,Ставка налогов на продукт,
Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Налоговый Подробная таблица выбирается из мастера элемента в виде строки и хранится в этой области.\n Используется по налогам и сборам, Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Налоговый Подробная таблица выбирается из мастера элемента в виде строки и хранится в этой области.\n Используется по налогам и сборам,
Purchase Order Item,Заказ товара, Purchase Order Item,Заказ товара,
Purchase Receipt Detail,Деталь квитанции о покупке, Purchase Receipt Detail,Сведения о квитанции о покупке,
Item Weight Details,Деталь Вес Подробности, Item Weight Details,Сведения о весе товара,
Weight Per Unit,Вес на единицу, Weight Per Unit,Вес на единицу,
Total Weight,Общий вес, Total Weight,Общий вес,
Weight UOM,Вес Единица измерения, Weight UOM,Вес Единица измерения,
@ -5198,7 +5198,7 @@ Address and Contacts,Адрес и контакты,
Contact List,Список контактов, Contact List,Список контактов,
Hidden list maintaining the list of contacts linked to Shareholder,"Скрытый список, поддерживающий список контактов, связанных с Акционером", Hidden list maintaining the list of contacts linked to Shareholder,"Скрытый список, поддерживающий список контактов, связанных с Акционером",
Specify conditions to calculate shipping amount,Укажите условия для расчета суммы доставки, Specify conditions to calculate shipping amount,Укажите условия для расчета суммы доставки,
Shipping Rule Label,Название правила доставки, Shipping Rule Label,Метка правила доставки,
example: Next Day Shipping,Пример: доставка на следующий день, example: Next Day Shipping,Пример: доставка на следующий день,
Shipping Rule Type,Тип правила доставки, Shipping Rule Type,Тип правила доставки,
Shipping Account,Счет доставки, Shipping Account,Счет доставки,
@ -5236,7 +5236,7 @@ Billing Interval,Интервал выставления счетов,
Billing Interval Count,Счет интервала фактурирования, Billing Interval Count,Счет интервала фактурирования,
"Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days","Количество интервалов для поля интервалов, например, если Interval является «Days», а количество интервалов фактурирования - 3, счета-фактуры будут генерироваться каждые 3 дня", "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days","Количество интервалов для поля интервалов, например, если Interval является «Days», а количество интервалов фактурирования - 3, счета-фактуры будут генерироваться каждые 3 дня",
Payment Plan,Платежный план, Payment Plan,Платежный план,
Subscription Plan Detail,Деталь плана подписки, Subscription Plan Detail,Сведения о плана подписки,
Plan,План, Plan,План,
Subscription Settings,Настройки подписки, Subscription Settings,Настройки подписки,
Grace Period,Льготный период, Grace Period,Льготный период,
@ -5802,7 +5802,7 @@ Make Academic Term Mandatory,Сделать академический срок
Skip User creation for new Student,Пропустить создание пользователя для нового студента, Skip User creation for new Student,Пропустить создание пользователя для нового студента,
"By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.","По умолчанию для каждого нового Студента создается новый Пользователь. Если этот параметр включен, при создании нового Студента новый Пользователь не создается.", "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.","По умолчанию для каждого нового Студента создается новый Пользователь. Если этот параметр включен, при создании нового Студента новый Пользователь не создается.",
Instructor Records to be created by,Записи инструкторов должны быть созданы, Instructor Records to be created by,Записи инструкторов должны быть созданы,
Employee Number,Общее число сотрудников, Employee Number,Номер сотрудника,
Fee Category,Категория платы, Fee Category,Категория платы,
Fee Component,Компонент платы, Fee Component,Компонент платы,
Fees Category,Категория плат, Fees Category,Категория плат,
@ -6196,7 +6196,7 @@ Inpatient Occupancy,Стационарное размещение,
Occupancy Status,Статус занятости, Occupancy Status,Статус занятости,
Vacant,Вакантно, Vacant,Вакантно,
Occupied,Занято, Occupied,Занято,
Item Details,Детальная информация о товаре, Item Details,Детальная информация о продукте,
UOM Conversion in Hours,Преобразование UOM в часы, UOM Conversion in Hours,Преобразование UOM в часы,
Rate / UOM,Скорость / UOM, Rate / UOM,Скорость / UOM,
Change in Item,Изменение продукта, Change in Item,Изменение продукта,
@ -6868,8 +6868,8 @@ Only Tax Impact (Cannot Claim But Part of Taxable Income),Только нало
Create Separate Payment Entry Against Benefit Claim,Создать отдельную заявку на подачу заявки на получение пособия, Create Separate Payment Entry Against Benefit Claim,Создать отдельную заявку на подачу заявки на получение пособия,
Condition and Formula,Состояние и формула, Condition and Formula,Состояние и формула,
Amount based on formula,Сумма на основе формулы, Amount based on formula,Сумма на основе формулы,
Formula,формула, Formula,Формула,
Salary Detail,Заработная плата: Подробности, Salary Detail,Подробно об заработной плате,
Component,Компонент, Component,Компонент,
Do not include in total,Не включать в общей сложности, Do not include in total,Не включать в общей сложности,
Default Amount,По умолчанию количество, Default Amount,По умолчанию количество,
@ -6891,7 +6891,7 @@ Total Principal Amount,Общая сумма,
Total Interest Amount,Общая сумма процентов, Total Interest Amount,Общая сумма процентов,
Total Loan Repayment,Общая сумма погашения кредита, Total Loan Repayment,Общая сумма погашения кредита,
net pay info,Чистая информация платить, net pay info,Чистая информация платить,
Gross Pay - Total Deduction - Loan Repayment,Gross Pay - Итого Вычет - Погашение кредита, Gross Pay - Total Deduction - Loan Repayment,Валовая заработная плата - Общий вычет - Погашение кредита,
Total in words,Всего в словах, Total in words,Всего в словах,
Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.", Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.",
Salary Component for timesheet based payroll.,Компонент заработной платы для расчета зарплаты на основе расписания., Salary Component for timesheet based payroll.,Компонент заработной платы для расчета зарплаты на основе расписания.,
@ -6961,7 +6961,7 @@ Trainer Email,Электронная почта тренера,
Attendees,Присутствующие, Attendees,Присутствующие,
Employee Emails,Электронные почты сотрудников, Employee Emails,Электронные почты сотрудников,
Training Event Employee,Обучение сотрудников Событие, Training Event Employee,Обучение сотрудников Событие,
Invited,приглашенный, Invited,Приглашенный,
Feedback Submitted,Отзыв отправлен, Feedback Submitted,Отзыв отправлен,
Optional,Необязательный, Optional,Необязательный,
Training Result Employee,Результат обучения сотрудника, Training Result Employee,Результат обучения сотрудника,
@ -7185,7 +7185,7 @@ Ordered Quantity,Заказанное количество,
Item to be manufactured or repacked,Продукт должен быть произведен или переупакован, Item to be manufactured or repacked,Продукт должен быть произведен или переупакован,
Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья, Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья,
Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации, Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации,
Allow Alternative Item,Разрешить альтернативный элемент, Allow Alternative Item,Разрешить альтернативный продукт,
Item UOM,Единиц продукта, Item UOM,Единиц продукта,
Conversion Rate,Коэффициент конверсии, Conversion Rate,Коэффициент конверсии,
Rate Of Materials Based On,Оценить материалов на основе, Rate Of Materials Based On,Оценить материалов на основе,
@ -7600,7 +7600,7 @@ Invoices with no Place Of Supply,Счета без места поставки,
Import Supplier Invoice,Импортная накладная поставщика, Import Supplier Invoice,Импортная накладная поставщика,
Invoice Series,Серия счетов, Invoice Series,Серия счетов,
Upload XML Invoices,Загрузить XML-счета, Upload XML Invoices,Загрузить XML-счета,
Zip File,Zip-файл, Zip File,Zip файл,
Import Invoices,Импорт счетов, Import Invoices,Импорт счетов,
Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.,"Нажмите кнопку «Импортировать счета-фактуры», когда файл zip прикреплен к документу. Любые ошибки, связанные с обработкой, будут отображаться в журнале ошибок.", Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.,"Нажмите кнопку «Импортировать счета-фактуры», когда файл zip прикреплен к документу. Любые ошибки, связанные с обработкой, будут отображаться в журнале ошибок.",
Lower Deduction Certificate,Свидетельство о нижнем удержании, Lower Deduction Certificate,Свидетельство о нижнем удержании,
@ -7635,7 +7635,7 @@ Restaurant Order Entry Item,Номер заказа заказа рестора
Served,Подается, Served,Подается,
Restaurant Reservation,Бронирование ресторанов, Restaurant Reservation,Бронирование ресторанов,
Waitlisted,Лист ожидания, Waitlisted,Лист ожидания,
No Show,Нет шоу, No Show,Не показывать,
No of People,Нет людей, No of People,Нет людей,
Reservation Time,Время резервирования, Reservation Time,Время резервирования,
Reservation End Time,Время окончания бронирования, Reservation End Time,Время окончания бронирования,
@ -7873,8 +7873,8 @@ Disable In Words,Отключить в словах,
"If disable, 'In Words' field will not be visible in any transaction","Если отключить, &quot;В словах&quot; поле не будет видно в любой сделке", "If disable, 'In Words' field will not be visible in any transaction","Если отключить, &quot;В словах&quot; поле не будет видно в любой сделке",
Item Classification,Продуктовая классификация, Item Classification,Продуктовая классификация,
General Settings,Основные настройки, General Settings,Основные настройки,
Item Group Name,Пункт Название группы, Item Group Name,Название группы продуктов,
Parent Item Group,Родитель Пункт Группа, Parent Item Group,Родительская группа продукта,
Item Group Defaults,Элемент группы по умолчанию, Item Group Defaults,Элемент группы по умолчанию,
Item Tax,Налог на продукт, Item Tax,Налог на продукт,
Check this if you want to show in website,"Проверьте это, если вы хотите показать в веб-сайт", Check this if you want to show in website,"Проверьте это, если вы хотите показать в веб-сайт",
@ -7971,13 +7971,13 @@ Customs Tariff Number,Номер таможенного тарифа,
Tariff Number,Тарифный номер, Tariff Number,Тарифный номер,
Delivery To,Доставка, Delivery To,Доставка,
MAT-DN-.YYYY.-,MAT-DN-.YYYY.-, MAT-DN-.YYYY.-,MAT-DN-.YYYY.-,
Is Return,Является Вернуться, Is Return,Возврат,
Issue Credit Note,Кредитная кредитная карта, Issue Credit Note,Кредитная кредитная карта,
Return Against Delivery Note,Вернуться На накладной, Return Against Delivery Note,Возврат по накладной,
Customer's Purchase Order No,Клиентам Заказ Нет, Customer's Purchase Order No,Заказ клиента №,
Billing Address Name,Название адреса для выставления счета, Billing Address Name,Название адреса для выставления счета,
Required only for sample item.,Требуется только для образца пункта., Required only for sample item.,Требуется только для образца пункта.,
"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.", "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в Шаблоне налогов и сборов с продаж, выберите его и нажмите кнопку ниже.",
In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной., In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной.,
In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной., In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной.,
Transporter Info,Информация для транспортировки, Transporter Info,Информация для транспортировки,
@ -7991,8 +7991,8 @@ Installation Status,Состояние установки,
Excise Page Number,Количество Акцизный Страница, Excise Page Number,Количество Акцизный Страница,
Instructions,Инструкции, Instructions,Инструкции,
From Warehouse,Со склада, From Warehouse,Со склада,
Against Sales Order,По Сделке, Against Sales Order,По сделке,
Against Sales Order Item,По Продукту Сделки, Against Sales Order Item,По позиции сделки,
Against Sales Invoice,Повторная накладная, Against Sales Invoice,Повторная накладная,
Against Sales Invoice Item,Счет на продажу продукта, Against Sales Invoice Item,Счет на продажу продукта,
Available Batch Qty at From Warehouse,Доступные Пакетная Кол-во на со склада, Available Batch Qty at From Warehouse,Доступные Пакетная Кол-во на со склада,
@ -8008,7 +8008,7 @@ Delivery Stop,Остановить доставку,
Lock,Заблокировано, Lock,Заблокировано,
Visited,Посещен, Visited,Посещен,
Order Information,запросить информацию, Order Information,запросить информацию,
Contact Information,Контакты, Contact Information,Контактная информация,
Email sent to,Письмо отправлено, Email sent to,Письмо отправлено,
Dispatch Information,Информация о доставке, Dispatch Information,Информация о доставке,
Estimated Arrival,Ожидаемое прибытие, Estimated Arrival,Ожидаемое прибытие,
@ -8121,7 +8121,7 @@ Two-way,Двусторонний,
Alternative Item Name,Альтернативное название продукта, Alternative Item Name,Альтернативное название продукта,
Attribute Name,Название атрибута, Attribute Name,Название атрибута,
Numeric Values,Числовые значения, Numeric Values,Числовые значения,
From Range,От хребта, From Range,Из диапазона,
Increment,Приращение, Increment,Приращение,
To Range,В диапазоне, To Range,В диапазоне,
Item Attribute Values,Пункт значений атрибутов, Item Attribute Values,Пункт значений атрибутов,
@ -8143,7 +8143,7 @@ Default Supplier,Поставщик по умолчанию,
Default Expense Account,Счет учета затрат по умолчанию, Default Expense Account,Счет учета затрат по умолчанию,
Sales Defaults,По умолчанию, Sales Defaults,По умолчанию,
Default Selling Cost Center,По умолчанию Продажа Стоимость центр, Default Selling Cost Center,По умолчанию Продажа Стоимость центр,
Item Manufacturer,Пункт Производитель, Item Manufacturer,Производитель товара,
Item Price,Цена продукта, Item Price,Цена продукта,
Packing Unit,Упаковочный блок, Packing Unit,Упаковочный блок,
Quantity that must be bought or sold per UOM,"Количество, которое необходимо купить или продать за UOM", Quantity that must be bought or sold per UOM,"Количество, которое необходимо купить или продать за UOM",
@ -8177,7 +8177,7 @@ Purchase Receipts,Покупка Поступления,
Purchase Receipt Items,Покупка продуктов, Purchase Receipt Items,Покупка продуктов,
Get Items From Purchase Receipts,Получить продукты из покупки., Get Items From Purchase Receipts,Получить продукты из покупки.,
Distribute Charges Based On,Распределите платежи на основе, Distribute Charges Based On,Распределите платежи на основе,
Landed Cost Help,Земельные Стоимость Помощь, Landed Cost Help,Справка по стоимости доставки,
Manufacturers used in Items,Производители использовали в пунктах, Manufacturers used in Items,Производители использовали в пунктах,
Limited to 12 characters,Ограничено до 12 символов, Limited to 12 characters,Ограничено до 12 символов,
MAT-MR-.YYYY.-,МАТ-MR-.YYYY.-, MAT-MR-.YYYY.-,МАТ-MR-.YYYY.-,
@ -8186,13 +8186,13 @@ Transferred,Переданы,
% Ordered,% заказано, % Ordered,% заказано,
Terms and Conditions Content,Условия Содержимое, Terms and Conditions Content,Условия Содержимое,
Quantity and Warehouse,Количество и Склад, Quantity and Warehouse,Количество и Склад,
Lead Time Date,Время и Дата Лида, Lead Time Date,Дата выполнения заказа,
Min Order Qty,Минимальный заказ Кол-во, Min Order Qty,Минимальное количество для заказа,
Packed Item,Упаковано, Packed Item,Упаковано,
To Warehouse (Optional),На склад (Необязательно), To Warehouse (Optional),На склад (Необязательно),
Actual Batch Quantity,Фактическое количество партий, Actual Batch Quantity,Фактическое количество партий,
Prevdoc DocType,Prevdoc DocType, Prevdoc DocType,Prevdoc DocType,
Parent Detail docname,Родитель Деталь DOCNAME, Parent Detail docname,Сведения о родителе docname,
"Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Создаёт упаковочные листы к упаковкам для доставки. Содержит номер упаковки, перечень содержимого и вес.", "Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Создаёт упаковочные листы к упаковкам для доставки. Содержит номер упаковки, перечень содержимого и вес.",
Indicates that the package is a part of this delivery (Only Draft),"Указывает, что пакет является частью этой поставки (только проект)", Indicates that the package is a part of this delivery (Only Draft),"Указывает, что пакет является частью этой поставки (только проект)",
MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-, MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-,
@ -8353,7 +8353,7 @@ Automatically Set Serial Nos based on FIFO,Автоматически устан
Auto Material Request,Автоматический запрос материалов, Auto Material Request,Автоматический запрос материалов,
Inter Warehouse Transfer Settings,Настройки передачи между складами, Inter Warehouse Transfer Settings,Настройки передачи между складами,
Freeze Stock Entries,Замораживание поступления запасов, Freeze Stock Entries,Замораживание поступления запасов,
Stock Frozen Upto,остатки заморожены до, Stock Frozen Upto,Остатки заморожены до,
Batch Identification,Идентификация партии, Batch Identification,Идентификация партии,
Use Naming Series,Использовать серийный номер, Use Naming Series,Использовать серийный номер,
Naming Series Prefix,Префикс Идентификации по Имени, Naming Series Prefix,Префикс Идентификации по Имени,
@ -8372,7 +8372,7 @@ Issue Split From,Выпуск Сплит От,
Service Level,Уровень обслуживания, Service Level,Уровень обслуживания,
Response By,Ответ от, Response By,Ответ от,
Response By Variance,Ответ по отклонениям, Response By Variance,Ответ по отклонениям,
Ongoing,постоянный, Ongoing,Постоянный,
Resolution By,Разрешение по, Resolution By,Разрешение по,
Resolution By Variance,Разрешение по отклонениям, Resolution By Variance,Разрешение по отклонениям,
Service Level Agreement Creation,Создание соглашения об уровне обслуживания, Service Level Agreement Creation,Создание соглашения об уровне обслуживания,

Can't render this file because it is too large.