Merge branch 'develop' into subcontracting

This commit is contained in:
Sagar Sharma 2022-05-23 11:35:50 +05:30 committed by GitHub
commit 6e5effc1b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 108 deletions

View File

@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate, nowdate
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
from erpnext.hr.utils import set_employee_name, validate_active_employee
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import (
@ -107,7 +107,10 @@ class LeaveEncashment(Document):
self.leave_balance = (
allocation.total_leaves_allocated
- allocation.carry_forwarded_leaves_count
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
# adding this because the function returns a -ve number
+ get_leaves_for_period(
self.employee, self.leave_type, allocation.from_date, self.encashment_date
)
)
encashable_days = self.leave_balance - frappe.db.get_value(
@ -126,14 +129,25 @@ class LeaveEncashment(Document):
return True
def get_leave_allocation(self):
leave_allocation = frappe.db.sql(
"""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
between from_date and to_date and docstatus=1 and leave_type='{1}'
and employee= '{2}'""".format(
self.encashment_date or getdate(nowdate()), self.leave_type, self.employee
),
as_dict=1,
) # nosec
date = self.encashment_date or getdate()
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
leave_allocation = (
frappe.qb.from_(LeaveAllocation)
.select(
LeaveAllocation.name,
LeaveAllocation.from_date,
LeaveAllocation.to_date,
LeaveAllocation.total_leaves_allocated,
LeaveAllocation.carry_forwarded_leaves_count,
)
.where(
((LeaveAllocation.from_date <= date) & (date <= LeaveAllocation.to_date))
& (LeaveAllocation.docstatus == 1)
& (LeaveAllocation.leave_type == self.leave_type)
& (LeaveAllocation.employee == self.employee)
)
).run(as_dict=True)
return leave_allocation[0] if leave_allocation else None

View File

@ -4,26 +4,42 @@
import unittest
import frappe
from frappe.utils import add_months, today
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, get_year_ending, get_year_start, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
create_assignment_for_multiple_employees,
)
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,
)
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
test_dependencies = ["Leave Type"]
test_records = frappe.get_test_records("Leave Type")
class TestLeaveEncashment(unittest.TestCase):
class TestLeaveEncashment(FrappeTestCase):
def setUp(self):
frappe.db.sql("""delete from `tabLeave Period`""")
frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
frappe.db.sql("""delete from `tabLeave Allocation`""")
frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
frappe.db.sql("""delete from `tabAdditional Salary`""")
frappe.db.delete("Leave Period")
frappe.db.delete("Leave Policy Assignment")
frappe.db.delete("Leave Allocation")
frappe.db.delete("Leave Ledger Entry")
frappe.db.delete("Additional Salary")
frappe.db.delete("Leave Encashment")
if not frappe.db.exists("Leave Type", "_Test Leave Type Encashment"):
frappe.get_doc(test_records[2]).insert()
date = getdate()
year_start = getdate(get_year_start(date))
year_end = getdate(get_year_ending(date))
make_holiday_list("_Test Leave Encashment", year_start, year_end)
# create the leave policy
leave_policy = create_leave_policy(
@ -32,9 +48,9 @@ class TestLeaveEncashment(unittest.TestCase):
leave_policy.submit()
# create employee, salary structure and assignment
self.employee = make_employee("test_employee_encashment@example.com")
self.employee = make_employee("test_employee_encashment@example.com", company="_Test Company")
self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
self.leave_period = create_leave_period(year_start, year_end, "_Test Company")
data = {
"assignment_based_on": "Leave Period",
@ -53,27 +69,15 @@ class TestLeaveEncashment(unittest.TestCase):
other_details={"leave_encashment_amount_per_day": 50},
)
def tearDown(self):
for dt in [
"Leave Period",
"Leave Allocation",
"Leave Ledger Entry",
"Additional Salary",
"Leave Encashment",
"Salary Structure",
"Leave Policy",
]:
frappe.db.sql("delete from `tab%s`" % dt)
@set_holiday_list("_Test Leave Encashment", "_Test Company")
def test_leave_balance_value_and_amount(self):
frappe.db.sql("""delete from `tabLeave Encashment`""")
leave_encashment = frappe.get_doc(
dict(
doctype="Leave Encashment",
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today(),
encashment_date=self.leave_period.to_date,
currency="INR",
)
).insert()
@ -88,15 +92,46 @@ class TestLeaveEncashment(unittest.TestCase):
add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
self.assertTrue(add_sal)
def test_creation_of_leave_ledger_entry_on_submit(self):
frappe.db.sql("""delete from `tabLeave Encashment`""")
@set_holiday_list("_Test Leave Encashment", "_Test Company")
def test_leave_balance_value_with_leaves_and_amount(self):
date = self.leave_period.from_date
leave_application = make_leave_application(
self.employee, date, add_days(date, 3), "_Test Leave Type Encashment"
)
leave_application.reload()
leave_encashment = frappe.get_doc(
dict(
doctype="Leave Encashment",
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today(),
encashment_date=self.leave_period.to_date,
currency="INR",
)
).insert()
self.assertEqual(leave_encashment.leave_balance, 10 - leave_application.total_leave_days)
# encashable days threshold is 5, total leaves are 6, so encashable days = 6-5 = 1
# with charge of 50 per day
self.assertEqual(leave_encashment.encashable_days, leave_encashment.leave_balance - 5)
self.assertEqual(leave_encashment.encashment_amount, 50)
leave_encashment.submit()
# assert links
add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
self.assertTrue(add_sal)
@set_holiday_list("_Test Leave Encashment", "_Test Company")
def test_creation_of_leave_ledger_entry_on_submit(self):
leave_encashment = frappe.get_doc(
dict(
doctype="Leave Encashment",
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
encashment_date=self.leave_period.to_date,
currency="INR",
)
).insert()

View File

@ -866,6 +866,7 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
target.set("time_logs", [])
target.set("employee", [])
target.set("items", [])
target.set("sub_operations", [])
target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()

View File

@ -1,15 +1,24 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import random_string
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError
from typing import Literal
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import random_string
from frappe.utils.data import add_to_date, now
from erpnext.manufacturing.doctype.job_card.job_card import (
OperationMismatchError,
OverlapError,
make_corrective_job_card,
)
from erpnext.manufacturing.doctype.job_card.job_card import (
make_stock_entry as make_stock_entry_from_jc,
)
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@ -17,34 +26,36 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestJobCard(FrappeTestCase):
def setUp(self):
make_bom_for_jc_tests()
self.transfer_material_against: Literal["Work Order", "Job Card"] = "Work Order"
self.source_warehouse = None
self._work_order = None
transfer_material_against, source_warehouse = None, None
@property
def work_order(self) -> WorkOrder:
"""Work Order lazily created for tests."""
if not self._work_order:
self._work_order = make_wo_order_test_record(
item="_Test FG Item 2",
qty=2,
transfer_material_against=self.transfer_material_against,
source_warehouse=self.source_warehouse,
)
return self._work_order
tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
tests_that_transfer_against_jc = (
"test_job_card_multiple_materials_transfer",
"test_job_card_excess_material_transfer",
"test_job_card_partial_material_transfer",
)
if self._testMethodName in tests_that_skip_setup:
return
if self._testMethodName in tests_that_transfer_against_jc:
transfer_material_against = "Job Card"
source_warehouse = "Stores - _TC"
self.work_order = make_wo_order_test_record(
item="_Test FG Item 2",
qty=2,
transfer_material_against=transfer_material_against,
source_warehouse=source_warehouse,
)
def generate_required_stock(self, work_order: WorkOrder) -> None:
"""Create twice the stock for all required items in work order."""
for item in work_order.required_items:
make_stock_entry(
item_code=item.item_code,
target=item.source_warehouse or self.source_warehouse,
qty=item.required_qty * 2,
basic_rate=100,
)
def tearDown(self):
frappe.db.rollback()
def test_job_card(self):
def test_job_card_operations(self):
job_cards = frappe.get_all(
"Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
@ -58,9 +69,6 @@ class TestJobCard(FrappeTestCase):
doc.operation_id = "Test Data"
self.assertRaises(OperationMismatchError, doc.save)
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
job_cards = frappe.get_all(
"Job Card",
@ -96,19 +104,11 @@ class TestJobCard(FrappeTestCase):
)
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_overlap(self):
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
jc1 = frappe.get_doc("Job Card", jc1_name)
jc2 = frappe.get_doc("Job Card", jc2_name)
jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
employee = "_T-Employee-00001" # from test records
@ -137,10 +137,10 @@ class TestJobCard(FrappeTestCase):
def test_job_card_multiple_materials_transfer(self):
"Test transferring RMs separately against Job Card with multiple RMs."
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
)
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})
job_card = frappe.get_doc("Job Card", job_card_name)
@ -167,22 +167,21 @@ class TestJobCard(FrappeTestCase):
def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card."
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
)
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
self.generate_required_stock(self.work_order)
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
self.assertEqual(job_card.status, "Open")
# fully transfer both RMs
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
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)
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
@ -195,7 +194,7 @@ class TestJobCard(FrappeTestCase):
# Check if 'For Quantity' is negative
# as 'transferred_qty' > Qty to Manufacture
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
transfer_entry_3 = make_stock_entry_from_jc(job_card.name)
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
job_card.append(
@ -210,17 +209,15 @@ class TestJobCard(FrappeTestCase):
def test_job_card_partial_material_transfer(self):
"Test partial material transfer against Job Card"
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
)
self.generate_required_stock(self.work_order)
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
# partially transfer
transfer_entry = make_stock_entry_from_jc(job_card_name)
transfer_entry = make_stock_entry_from_jc(job_card.name)
transfer_entry.fg_completed_qty = 1
transfer_entry.get_items()
transfer_entry.insert()
@ -232,7 +229,7 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(transfer_entry.items[1].qty, 3)
# transfer remaining
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
self.assertEqual(transfer_entry_2.fg_completed_qty, 1)
self.assertEqual(transfer_entry_2.items[0].qty, 5)
@ -277,7 +274,49 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(transfer_entry.items[0].item_code, "_Test Item")
self.assertEqual(transfer_entry.items[0].qty, 2)
# rollback via tearDown method
@change_settings(
"Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1}
)
def test_corrective_costing(self):
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
job_card.append(
"time_logs",
{"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2},
)
job_card.submit()
self.work_order.reload()
original_cost = self.work_order.total_operating_cost
# Create a corrective operation against it
corrective_action = frappe.get_doc(
doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
).insert()
corrective_job_card = make_corrective_job_card(
job_card.name, operation=corrective_action.name, for_operation=job_card.operation
)
corrective_job_card.hour_rate = 100
corrective_job_card.insert()
corrective_job_card.append(
"time_logs",
{
"from_time": add_to_date(now(), hours=2),
"to_time": add_to_date(now(), hours=2, minutes=30),
"completed_qty": 2,
},
)
corrective_job_card.submit()
self.work_order.reload()
cost_after_correction = self.work_order.total_operating_cost
self.assertGreater(cost_after_correction, original_cost)
corrective_job_card.cancel()
self.work_order.reload()
cost_after_cancel = self.work_order.total_operating_cost
self.assertEqual(cost_after_cancel, original_cost)
def create_bom_with_multiple_operations():

View File

@ -1701,7 +1701,7 @@ No Permission,Keine Berechtigung,
No Remarks,Keine Anmerkungen,
No Result to submit,Kein Ergebnis zur Einreichung,
No Salary Structure assigned for Employee {0} on given date {1},Keine Gehaltsstruktur für Mitarbeiter {0} am angegebenen Datum {1} zugewiesen,
No Staffing Plans found for this Designation,Für diese Bezeichnung wurden keine Stellenpläne gefunden,
No Staffing Plans found for this Designation,Für diese Position wurden keine Stellenpläne gefunden,
No Student Groups created.,Keine Studentengruppen erstellt.,
No Students in,Keine Studenten in,
No Tax Withholding data found for the current Fiscal Year.,Keine Steuerverweigerungsdaten für das aktuelle Geschäftsjahr gefunden.,
@ -2027,7 +2027,7 @@ Please select BOM in BOM field for Item {0},Bitte aus dem Stücklistenfeld eine
Please select Category first,Bitte zuerst Kategorie auswählen,
Please select Charge Type first,Bitte zuerst Chargentyp auswählen,
Please select Company,Bitte Unternehmen auswählen,
Please select Company and Designation,Bitte wählen Sie Unternehmen und Stelle,
Please select Company and Designation,Bitte wählen Sie Unternehmen und Position,
Please select Company and Posting Date to getting entries,"Bitte wählen Sie Unternehmen und Buchungsdatum, um Einträge zu erhalten",
Please select Company first,Bitte zuerst Unternehmen auswählen,
Please select Completion Date for Completed Asset Maintenance Log,Bitte wählen Sie Fertigstellungsdatum für das abgeschlossene Wartungsprotokoll für den Vermögenswert,
@ -2772,7 +2772,7 @@ Split,Teilt,
Split Batch,Split Batch,
Split Issue,Split-Problem,
Sports,Sport,
Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Bezeichnung {1},
Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Position {1},
Standard,Standard,
Standard Buying,Standard-Kauf,
Standard Selling,Standard-Vertrieb,
@ -3710,7 +3710,7 @@ Delivered Quantity,Gelieferte Menge,
Delivery Notes,Lieferscheine,
Depreciated Amount,Abschreibungsbetrag,
Description,Beschreibung,
Designation,Bezeichnung,
Designation,Position,
Difference Value,Differenzwert,
Dimension Filter,Dimensionsfilter,
Disabled,Deaktiviert,
@ -3920,7 +3920,7 @@ Please enter <b>Difference Account</b> or set default <b>Stock Adjustment Accoun
Please enter GSTIN and state for the Company Address {0},Bitte geben Sie GSTIN ein und geben Sie die Firmenadresse {0} an.,
Please enter Item Code to get item taxes,"Bitte geben Sie den Artikelcode ein, um die Artikelsteuern zu erhalten",
Please enter Warehouse and Date,Bitte geben Sie Lager und Datum ein,
Please enter the designation,Bitte geben Sie die Bezeichnung ein,
Please enter the designation,Bitte geben Sie die Position ein,
Please login as a Marketplace User to edit this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu bearbeiten.",
Please login as a Marketplace User to report this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu melden.",
Please select <b>Template Type</b> to download template,"Bitte wählen Sie <b>Vorlagentyp</b> , um die Vorlage herunterzuladen",
@ -6243,7 +6243,7 @@ Checking this will create Lab Test(s) specified in the Sales Invoice on submissi
Create Sample Collection document for Lab Test,Erstellen Sie ein Probensammeldokument für den Labortest,
Checking this will create a Sample Collection document every time you create a Lab Test,"Wenn Sie dies aktivieren, wird jedes Mal, wenn Sie einen Labortest erstellen, ein Probensammeldokument erstellt",
Employee name and designation in print,Name und Bezeichnung des Mitarbeiters im Druck,
Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Bezeichnung des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.",
Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Position des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.",
Do not print or email Lab Tests without Approval,Drucken oder senden Sie Labortests nicht ohne Genehmigung per E-Mail,
Checking this will restrict printing and emailing of Lab Test documents unless they have the status as Approved.,"Wenn Sie dies aktivieren, wird das Drucken und E-Mailen von Labortestdokumenten eingeschränkt, sofern diese nicht den Status &quot;Genehmigt&quot; haben.",
Custom Signature in Print,Kundenspezifische Unterschrift im Druck,
@ -6499,7 +6499,7 @@ Department Approver,Abteilungsgenehmiger,
Approver,Genehmiger,
Required Skills,Benötigte Fähigkeiten,
Skills,Kompetenzen,
Designation Skill,Bezeichnung Fähigkeit,
Designation Skill,Positions Fähigkeit,
Skill,Fertigkeit,
Driver,Fahrer/-in,
HR-DRI-.YYYY.-,HR-DRI-.YYYY.-,
@ -6798,7 +6798,7 @@ Select Employees,Mitarbeiter auswählen,
Employment Type (optional),Anstellungsart (optional),
Branch (optional),Zweigstelle (optional),
Department (optional),Abteilung (optional),
Designation (optional),Bezeichnung (optional),
Designation (optional),Position (optional),
Employee Grade (optional),Dienstgrad (optional),
Employee (optional),Mitarbeiter (optional),
Allocate Leaves,Blätter zuweisen,
@ -7769,7 +7769,7 @@ Authorized Value,Autorisierter Wert,
Applicable To (Role),Anwenden auf (Rolle),
Applicable To (Employee),Anwenden auf (Mitarbeiter),
Applicable To (User),Anwenden auf (Benutzer),
Applicable To (Designation),Anwenden auf (Bezeichnung),
Applicable To (Designation),Anwenden auf (Position),
Approving Role (above authorized value),Genehmigende Rolle (über dem autorisierten Wert),
Approving User (above authorized value),Genehmigender Benutzer (über dem autorisierten Wert),
Brand Defaults,Markenstandards,
@ -8946,7 +8946,7 @@ Requesting Practitioner,Praktizierender anfordern,
Requesting Department,Abteilung anfordern,
Employee (Lab Technician),Mitarbeiter (Labortechniker),
Lab Technician Name,Name des Labortechnikers,
Lab Technician Designation,Bezeichnung des Labortechnikers,
Lab Technician Designation,Position des Labortechnikers,
Compound Test Result,Zusammengesetztes Testergebnis,
Organism Test Result,Organismustestergebnis,
Sensitivity Test Result,Empfindlichkeitstestergebnis,

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