| Total Complexity | 114 |
| Total Lines | 955 |
| Duplicated Lines | 21.88 % |
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ajaxAnalysisRequestAddView often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # -*- coding: utf-8 -*- |
||
| 457 | class ajaxAnalysisRequestAddView(BaseAjaxAddView, AnalysisRequestAddView): |
||
| 458 | """Ajax helpers for the analysis request add form |
||
| 459 | """ |
||
| 460 | implements(IPublishTraverse) |
||
| 461 | |||
| 462 | def __init__(self, context, request): |
||
| 463 | AnalysisRequestAddView.__init__(self, context, request) |
||
| 464 | BaseAjaxAddView.__init__(self, context, request) |
||
| 465 | |||
| 466 | @cache(cache_key) |
||
| 467 | def get_client_info(self, obj): |
||
| 468 | """Returns the client info of an object |
||
| 469 | """ |
||
| 470 | info = self.get_base_info(obj) |
||
| 471 | info.update({}) |
||
| 472 | |||
| 473 | # UID of the client |
||
| 474 | uid = api.get_uid(obj) |
||
| 475 | |||
| 476 | # Bika Setup folder |
||
| 477 | bika_setup = api.get_bika_setup() |
||
| 478 | |||
| 479 | # bika samplepoints |
||
| 480 | bika_samplepoints = bika_setup.bika_samplepoints |
||
| 481 | bika_samplepoints_uid = api.get_uid(bika_samplepoints) |
||
| 482 | |||
| 483 | # bika artemplates |
||
| 484 | bika_artemplates = bika_setup.bika_artemplates |
||
| 485 | bika_artemplates_uid = api.get_uid(bika_artemplates) |
||
| 486 | |||
| 487 | # bika analysisprofiles |
||
| 488 | bika_analysisprofiles = bika_setup.bika_analysisprofiles |
||
| 489 | bika_analysisprofiles_uid = api.get_uid(bika_analysisprofiles) |
||
| 490 | |||
| 491 | # bika analysisspecs |
||
| 492 | bika_analysisspecs = bika_setup.bika_analysisspecs |
||
| 493 | bika_analysisspecs_uid = api.get_uid(bika_analysisspecs) |
||
| 494 | |||
| 495 | # catalog queries for UI field filtering |
||
| 496 | filter_queries = { |
||
| 497 | "contact": { |
||
| 498 | "getParentUID": [uid] |
||
| 499 | }, |
||
| 500 | "cc_contact": { |
||
| 501 | "getParentUID": [uid] |
||
| 502 | }, |
||
| 503 | "invoice_contact": { |
||
| 504 | "getParentUID": [uid] |
||
| 505 | }, |
||
| 506 | "samplepoint": { |
||
| 507 | "getClientUID": [uid, bika_samplepoints_uid], |
||
| 508 | }, |
||
| 509 | "artemplates": { |
||
| 510 | "getClientUID": [uid, bika_artemplates_uid], |
||
| 511 | }, |
||
| 512 | "analysisprofiles": { |
||
| 513 | "getClientUID": [uid, bika_analysisprofiles_uid], |
||
| 514 | }, |
||
| 515 | "analysisspecs": { |
||
| 516 | "getClientUID": [uid, bika_analysisspecs_uid], |
||
| 517 | }, |
||
| 518 | "samplinground": { |
||
| 519 | "getParentUID": [uid], |
||
| 520 | View Code Duplication | }, |
|
| 521 | "sample": { |
||
| 522 | "getClientUID": [uid], |
||
| 523 | }, |
||
| 524 | } |
||
| 525 | info["filter_queries"] = filter_queries |
||
| 526 | |||
| 527 | return info |
||
| 528 | |||
| 529 | @cache(cache_key) |
||
| 530 | def get_contact_info(self, obj): |
||
| 531 | """Returns the client info of an object |
||
| 532 | """ |
||
| 533 | |||
| 534 | info = self.get_base_info(obj) |
||
| 535 | fullname = obj.getFullname() |
||
| 536 | email = obj.getEmailAddress() |
||
| 537 | |||
| 538 | # Note: It might get a circular dependency when calling: |
||
| 539 | # map(self.get_contact_info, obj.getCCContact()) |
||
| 540 | cccontacts = {} |
||
| 541 | for contact in obj.getCCContact(): |
||
| 542 | uid = api.get_uid(contact) |
||
| 543 | fullname = contact.getFullname() |
||
| 544 | email = contact.getEmailAddress() |
||
| 545 | cccontacts[uid] = { |
||
| 546 | "fullname": fullname, |
||
| 547 | "email": email |
||
| 548 | } |
||
| 549 | |||
| 550 | info.update({ |
||
| 551 | "fullname": fullname, |
||
| 552 | "email": email, |
||
| 553 | "cccontacts": cccontacts, |
||
| 554 | }) |
||
| 555 | |||
| 556 | return info |
||
| 557 | |||
| 558 | @cache(cache_key) |
||
| 559 | def get_service_info(self, obj): |
||
| 560 | """Returns the info for a Service |
||
| 561 | """ |
||
| 562 | info = self.get_base_info(obj) |
||
| 563 | |||
| 564 | info.update({ |
||
| 565 | "short_title": obj.getShortTitle(), |
||
| 566 | "scientific_name": obj.getScientificName(), |
||
| 567 | "unit": obj.getUnit(), |
||
| 568 | "report_dry_matter": obj.getReportDryMatter(), |
||
| 569 | "keyword": obj.getKeyword(), |
||
| 570 | "methods": map(self.get_method_info, obj.getMethods()), |
||
| 571 | "calculation": self.get_calculation_info(obj.getCalculation()), |
||
| 572 | "price": obj.getPrice(), |
||
| 573 | "currency_symbol": self.get_currency().symbol, |
||
| 574 | "accredited": obj.getAccredited(), |
||
| 575 | "category": obj.getCategoryTitle(), |
||
| 576 | "poc": obj.getPointOfCapture(), |
||
| 577 | |||
| 578 | }) |
||
| 579 | View Code Duplication | ||
| 580 | dependencies = self.get_calculation_dependencies_for(obj).values() |
||
| 581 | info["dependencies"] = map(self.get_base_info, dependencies) |
||
| 582 | # dependants = self.get_calculation_dependants_for(obj).values() |
||
| 583 | # info["dependendants"] = map(self.get_base_info, dependants) |
||
| 584 | return info |
||
| 585 | |||
| 586 | @cache(cache_key) |
||
| 587 | def get_template_info(self, obj): |
||
| 588 | """Returns the info for a Template |
||
| 589 | """ |
||
| 590 | client = self.get_client() |
||
| 591 | client_uid = api.get_uid(client) if client else "" |
||
| 592 | |||
| 593 | profile = obj.getAnalysisProfile() |
||
| 594 | profile_uid = api.get_uid(profile) if profile else "" |
||
| 595 | profile_title = profile.Title() if profile else "" |
||
| 596 | |||
| 597 | sample_type = obj.getSampleType() |
||
| 598 | sample_type_uid = api.get_uid(sample_type) if sample_type else "" |
||
| 599 | sample_type_title = sample_type.Title() if sample_type else "" |
||
| 600 | |||
| 601 | sample_point = obj.getSamplePoint() |
||
| 602 | sample_point_uid = api.get_uid(sample_point) if sample_point else "" |
||
| 603 | sample_point_title = sample_point.Title() if sample_point else "" |
||
| 604 | |||
| 605 | service_uids = [] |
||
| 606 | analyses_partitions = {} |
||
| 607 | analyses = obj.getAnalyses() |
||
| 608 | |||
| 609 | for record in analyses: |
||
| 610 | service_uid = record.get("service_uid") |
||
| 611 | service_uids.append(service_uid) |
||
| 612 | analyses_partitions[service_uid] = record.get("partition") |
||
| 613 | |||
| 614 | info = self.get_base_info(obj) |
||
| 615 | info.update({ |
||
| 616 | "analyses_partitions": analyses_partitions, |
||
| 617 | "analysis_profile_title": profile_title, |
||
| 618 | "analysis_profile_uid": profile_uid, |
||
| 619 | "client_uid": client_uid, |
||
| 620 | "composite": obj.getComposite(), |
||
| 621 | "partitions": obj.getPartitions(), |
||
| 622 | "remarks": obj.getRemarks(), |
||
| 623 | "report_dry_matter": obj.getReportDryMatter(), |
||
| 624 | "sample_point_title": sample_point_title, |
||
| 625 | "sample_point_uid": sample_point_uid, |
||
| 626 | "sample_type_title": sample_type_title, |
||
| 627 | "sample_type_uid": sample_type_uid, |
||
| 628 | "service_uids": service_uids, |
||
| 629 | }) |
||
| 630 | return info |
||
| 631 | |||
| 632 | @cache(cache_key) |
||
| 633 | def get_profile_info(self, obj): |
||
| 634 | """Returns the info for a Profile |
||
| 635 | """ |
||
| 636 | info = self.get_base_info(obj) |
||
| 637 | info.update({}) |
||
| 638 | return info |
||
| 639 | |||
| 640 | @cache(cache_key) |
||
| 641 | def get_method_info(self, obj): |
||
| 642 | """Returns the info for a Method |
||
| 643 | """ |
||
| 644 | info = self.get_base_info(obj) |
||
| 645 | info.update({}) |
||
| 646 | return info |
||
| 647 | |||
| 648 | @cache(cache_key) |
||
| 649 | def get_calculation_info(self, obj): |
||
| 650 | """Returns the info for a Calculation |
||
| 651 | """ |
||
| 652 | info = self.get_base_info(obj) |
||
| 653 | info.update({}) |
||
| 654 | return info |
||
| 655 | |||
| 656 | View Code Duplication | @cache(cache_key) |
|
| 657 | def get_sampletype_info(self, obj): |
||
| 658 | """Returns the info for a Sample Type |
||
| 659 | """ |
||
| 660 | info = self.get_base_info(obj) |
||
| 661 | |||
| 662 | # Bika Setup folder |
||
| 663 | bika_setup = api.get_bika_setup() |
||
| 664 | |||
| 665 | # bika samplepoints |
||
| 666 | bika_samplepoints = bika_setup.bika_samplepoints |
||
| 667 | bika_samplepoints_uid = api.get_uid(bika_samplepoints) |
||
| 668 | |||
| 669 | # bika analysisspecs |
||
| 670 | bika_analysisspecs = bika_setup.bika_analysisspecs |
||
| 671 | bika_analysisspecs_uid = api.get_uid(bika_analysisspecs) |
||
| 672 | |||
| 673 | # client |
||
| 674 | client = self.get_client() |
||
| 675 | client_uid = client and api.get_uid(client) or "" |
||
| 676 | |||
| 677 | # sample matrix |
||
| 678 | sample_matrix = obj.getSampleMatrix() |
||
| 679 | sample_matrix_uid = sample_matrix and sample_matrix.UID() or "" |
||
| 680 | sample_matrix_title = sample_matrix and sample_matrix.Title() or "" |
||
| 681 | |||
| 682 | # container type |
||
| 683 | container_type = obj.getContainerType() |
||
| 684 | container_type_uid = container_type and container_type.UID() or "" |
||
| 685 | container_type_title = container_type and container_type.Title() or "" |
||
| 686 | |||
| 687 | # sample points |
||
| 688 | sample_points = obj.getSamplePoints() |
||
| 689 | sample_point_uids = map(lambda sp: sp.UID(), sample_points) |
||
| 690 | sample_point_titles = map(lambda sp: sp.Title(), sample_points) |
||
| 691 | |||
| 692 | info.update({ |
||
| 693 | "prefix": obj.getPrefix(), |
||
| 694 | "minimum_volume": obj.getMinimumVolume(), |
||
| 695 | "hazardous": obj.getHazardous(), |
||
| 696 | "retention_period": obj.getRetentionPeriod(), |
||
| 697 | "sample_matrix_uid": sample_matrix_uid, |
||
| 698 | "sample_matrix_title": sample_matrix_title, |
||
| 699 | "container_type_uid": container_type_uid, |
||
| 700 | "container_type_title": container_type_title, |
||
| 701 | "sample_point_uids": sample_point_uids, |
||
| 702 | "sample_point_titles": sample_point_titles, |
||
| 703 | }) |
||
| 704 | |||
| 705 | # catalog queries for UI field filtering |
||
| 706 | filter_queries = { |
||
| 707 | "samplepoint": { |
||
| 708 | "getSampleTypeTitles": [obj.Title(), ''], |
||
| 709 | "getClientUID": [client_uid, bika_samplepoints_uid], |
||
| 710 | "sort_order": "descending", |
||
| 711 | }, |
||
| 712 | "specification": { |
||
| 713 | "getSampleTypeTitle": obj.Title(), |
||
| 714 | "getClientUID": [client_uid, bika_analysisspecs_uid], |
||
| 715 | "sort_order": "descending", |
||
| 716 | } |
||
| 717 | } |
||
| 718 | info["filter_queries"] = filter_queries |
||
| 719 | |||
| 720 | return info |
||
| 721 | |||
| 722 | @cache(cache_key) |
||
| 723 | def get_sample_info(self, obj): |
||
| 724 | """Returns the info for a Sample |
||
| 725 | """ |
||
| 726 | info = self.get_base_info(obj) |
||
| 727 | |||
| 728 | # sample type |
||
| 729 | sample_type = obj.getSampleType() |
||
| 730 | sample_type_uid = sample_type and sample_type.UID() or "" |
||
| 731 | sample_type_title = sample_type and sample_type.Title() or "" |
||
| 732 | |||
| 733 | # sample condition |
||
| 734 | sample_condition = obj.getSampleCondition() |
||
| 735 | sample_condition_uid = sample_condition and sample_condition.UID() or "" |
||
| 736 | sample_condition_title = sample_condition and sample_condition.Title() or "" |
||
| 737 | |||
| 738 | # storage location |
||
| 739 | storage_location = obj.getStorageLocation() |
||
| 740 | storage_location_uid = storage_location and storage_location.UID() or "" |
||
| 741 | storage_location_title = storage_location and storage_location.Title() or "" |
||
| 742 | |||
| 743 | # sample point |
||
| 744 | sample_point = obj.getSamplePoint() |
||
| 745 | sample_point_uid = sample_point and sample_point.UID() or "" |
||
| 746 | sample_point_title = sample_point and sample_point.Title() or "" |
||
| 747 | |||
| 748 | # container type |
||
| 749 | container_type = sample_type and sample_type.getContainerType() or None |
||
| 750 | container_type_uid = container_type and container_type.UID() or "" |
||
| 751 | container_type_title = container_type and container_type.Title() or "" |
||
| 752 | |||
| 753 | info.update({ |
||
| 754 | "sample_id": obj.getSampleID(), |
||
| 755 | "date_sampled": self.to_iso_date(obj.getDateSampled()), |
||
| 756 | "sampling_date": self.to_iso_date(obj.getSamplingDate()), |
||
| 757 | "sample_type_uid": sample_type_uid, |
||
| 758 | "sample_type_title": sample_type_title, |
||
| 759 | "container_type_uid": container_type_uid, |
||
| 760 | View Code Duplication | "container_type_title": container_type_title, |
|
| 761 | "sample_condition_uid": sample_condition_uid, |
||
| 762 | "sample_condition_title": sample_condition_title, |
||
| 763 | "storage_location_uid": storage_location_uid, |
||
| 764 | "storage_location_title": storage_location_title, |
||
| 765 | "sample_point_uid": sample_point_uid, |
||
| 766 | "sample_point_title": sample_point_title, |
||
| 767 | "environmental_conditions": obj.getEnvironmentalConditions(), |
||
| 768 | "composite": obj.getComposite(), |
||
| 769 | "client_sample_id": obj.getClientSampleID(), |
||
| 770 | "client_reference": obj.getClientReference(), |
||
| 771 | "sampling_workflow_enabled": obj.getSamplingWorkflowEnabled(), |
||
| 772 | "adhoc": obj.getAdHoc(), |
||
| 773 | "remarks": obj.getRemarks(), |
||
| 774 | }) |
||
| 775 | return info |
||
| 776 | |||
| 777 | @cache(cache_key) |
||
| 778 | def get_specification_info(self, obj): |
||
| 779 | """Returns the info for a Specification |
||
| 780 | """ |
||
| 781 | info = self.get_base_info(obj) |
||
| 782 | |||
| 783 | results_range = obj.getResultsRange() |
||
| 784 | info.update({ |
||
| 785 | "results_range": results_range, |
||
| 786 | "sample_type_uid": obj.getSampleTypeUID(), |
||
| 787 | "sample_type_title": obj.getSampleTypeTitle(), |
||
| 788 | "client_uid": obj.getClientUID(), |
||
| 789 | }) |
||
| 790 | |||
| 791 | bsc = api.get_tool("bika_setup_catalog") |
||
| 792 | |||
| 793 | def get_service_by_keyword(keyword): |
||
| 794 | if keyword is None: |
||
| 795 | return [] |
||
| 796 | return map(api.get_object, bsc({ |
||
| 797 | "portal_type": "AnalysisService", |
||
| 798 | "getKeyword": keyword |
||
| 799 | })) |
||
| 800 | |||
| 801 | # append a mapping of service_uid -> specification |
||
| 802 | specifications = {} |
||
| 803 | for spec in results_range: |
||
| 804 | service_uid = spec.get("uid") |
||
| 805 | if service_uid is None: |
||
| 806 | # service spec is not attached to a specific service, but to a keyword |
||
| 807 | for service in get_service_by_keyword(spec.get("keyword")): |
||
| 808 | service_uid = api.get_uid(service) |
||
| 809 | specifications[service_uid] = spec |
||
| 810 | continue |
||
| 811 | specifications[service_uid] = spec |
||
| 812 | info["specifications"] = specifications |
||
| 813 | # spec'd service UIDs |
||
| 814 | info["service_uids"] = specifications.keys() |
||
| 815 | return info |
||
| 816 | |||
| 817 | @cache(cache_key) |
||
| 818 | def get_container_info(self, obj): |
||
| 819 | """Returns the info for a Container |
||
| 820 | """ |
||
| 821 | info = self.get_base_info(obj) |
||
| 822 | info.update({}) |
||
| 823 | return info |
||
| 824 | |||
| 825 | def get_service_partitions(self, service, sampletype): |
||
| 826 | """Returns the Partition info for a Service and SampleType |
||
| 827 | |||
| 828 | N.B.: This is actually not used as the whole partition, preservation |
||
| 829 | and conservation settings are solely handled by AR Templates for all |
||
| 830 | selected services. |
||
| 831 | """ |
||
| 832 | |||
| 833 | partitions = [] |
||
| 834 | |||
| 835 | sampletype_uid = api.get_uid(sampletype) |
||
| 836 | # partition setup of this service |
||
| 837 | partition_setup = filter(lambda p: p.get("sampletype") == sampletype_uid, |
||
| 838 | service.getPartitionSetup()) |
||
| 839 | |||
| 840 | def get_containers(container_uids): |
||
| 841 | containers = [] |
||
| 842 | for container_uid in container_uids: |
||
| 843 | container = api.get_object_by_uid(container_uid) |
||
| 844 | if container.portal_type == "ContainerTypes": |
||
| 845 | containers.extend(container.getContainers()) |
||
| 846 | else: |
||
| 847 | containers.append(container) |
||
| 848 | return containers |
||
| 849 | |||
| 850 | for partition in partition_setup: |
||
| 851 | containers = get_containers(partition.get("container", [])) |
||
| 852 | preservations = map(api.get_object_by_uid, partition.get("preservation", [])) |
||
| 853 | partitions.append({ |
||
| 854 | "separate": partition.get("separate", False) and True or False, |
||
| 855 | "container": map(self.get_container_info, containers), |
||
| 856 | "preservations": map(self.get_preservation_info, preservations), |
||
| 857 | "minvol": partition.get("vol", ""), |
||
| 858 | }) |
||
| 859 | else: |
||
| 860 | containers = [service.getContainer()] or [] |
||
| 861 | preservations = [service.getPreservation()] or [] |
||
| 862 | partitions.append({ |
||
| 863 | "separate": service.getSeparate(), |
||
| 864 | "container": map(self.get_container_info, containers), |
||
| 865 | "preservations": map(self.get_preservation_info, preservations), |
||
| 866 | "minvol": sampletype.getMinimumVolume() or "", |
||
| 867 | }) |
||
| 868 | |||
| 869 | return partitions |
||
| 870 | |||
| 871 | def ajax_get_service(self): |
||
| 872 | """Returns the services information |
||
| 873 | """ |
||
| 874 | uid = self.request.form.get("uid", None) |
||
| 875 | |||
| 876 | if uid is None: |
||
| 877 | return self.error("Invalid UID", status=400) |
||
| 878 | |||
| 879 | service = self.get_object_by_uid(uid) |
||
| 880 | if not service: |
||
| 881 | return self.error("Service not found", status=404) |
||
| 882 | |||
| 883 | info = self.get_service_info(service) |
||
| 884 | return info |
||
| 885 | |||
| 886 | def ajax_recalculate_records(self): |
||
| 887 | """Recalculate all AR records and dependencies |
||
| 888 | |||
| 889 | - samples |
||
| 890 | - templates |
||
| 891 | - profiles |
||
| 892 | - services |
||
| 893 | - dependecies |
||
| 894 | |||
| 895 | XXX: This function has grown too much and needs refactoring! |
||
| 896 | """ |
||
| 897 | out = {} |
||
| 898 | |||
| 899 | # The sorted records from the request |
||
| 900 | records = self.get_records() |
||
| 901 | |||
| 902 | for n, record in enumerate(records): |
||
| 903 | |||
| 904 | # Mapping of client UID -> client object info |
||
| 905 | client_metadata = {} |
||
| 906 | # Mapping of contact UID -> contact object info |
||
| 907 | contact_metadata = {} |
||
| 908 | # Mapping of sample UID -> sample object info |
||
| 909 | sample_metadata = {} |
||
| 910 | # Mapping of sampletype UID -> sampletype object info |
||
| 911 | sampletype_metadata = {} |
||
| 912 | # Mapping of drymatter UID -> drymatter service info |
||
| 913 | dms_metadata = {} |
||
| 914 | # Mapping of drymatter service (dms) -> list of dependent services |
||
| 915 | dms_to_services = {} |
||
| 916 | # Mapping of dependent services -> drymatter service (dms) |
||
| 917 | service_to_dms = {} |
||
| 918 | # Mapping of specification UID -> specification object info |
||
| 919 | specification_metadata = {} |
||
| 920 | # Mapping of specification UID -> list of service UIDs |
||
| 921 | specification_to_services = {} |
||
| 922 | # Mapping of service UID -> list of specification UIDs |
||
| 923 | service_to_specifications = {} |
||
| 924 | # Mapping of template UID -> template object info |
||
| 925 | template_metadata = {} |
||
| 926 | # Mapping of template UID -> list of service UIDs |
||
| 927 | template_to_services = {} |
||
| 928 | # Mapping of service UID -> list of template UIDs |
||
| 929 | service_to_templates = {} |
||
| 930 | # Mapping of profile UID -> list of service UIDs |
||
| 931 | profile_to_services = {} |
||
| 932 | # Mapping of service UID -> list of profile UIDs |
||
| 933 | service_to_profiles = {} |
||
| 934 | # Profile metadata for UI purposes |
||
| 935 | profile_metadata = {} |
||
| 936 | # Mapping of service UID -> service object info |
||
| 937 | service_metadata = {} |
||
| 938 | # mapping of service UID -> unmet service dependency UIDs |
||
| 939 | unmet_dependencies = {} |
||
| 940 | |||
| 941 | # Internal mappings of UID -> object of selected items in this record |
||
| 942 | _clients = self.get_objs_from_record(record, "Client_uid") |
||
| 943 | _contacts = self.get_objs_from_record(record, "Contact_uid") |
||
| 944 | _specifications = self.get_objs_from_record(record, "Specification_uid") |
||
| 945 | _templates = self.get_objs_from_record(record, "Template_uid") |
||
| 946 | _samples = self.get_objs_from_record(record, "Sample_uid") |
||
| 947 | _profiles = self.get_objs_from_record(record, "Profiles_uid") |
||
| 948 | _services = self.get_objs_from_record(record, "Analyses") |
||
| 949 | _sampletypes = self.get_objs_from_record(record, "SampleType_uid") |
||
| 950 | |||
| 951 | # CLIENTS |
||
| 952 | for uid, obj in _clients.iteritems(): |
||
| 953 | # get the client metadata |
||
| 954 | metadata = self.get_client_info(obj) |
||
| 955 | # remember the sampletype metadata |
||
| 956 | client_metadata[uid] = metadata |
||
| 957 | |||
| 958 | # CONTACTS |
||
| 959 | for uid, obj in _contacts.iteritems(): |
||
| 960 | # get the client metadata |
||
| 961 | metadata = self.get_contact_info(obj) |
||
| 962 | # remember the sampletype metadata |
||
| 963 | contact_metadata[uid] = metadata |
||
| 964 | |||
| 965 | # SPECIFICATIONS |
||
| 966 | for uid, obj in _specifications.iteritems(): |
||
| 967 | # get the specification metadata |
||
| 968 | metadata = self.get_specification_info(obj) |
||
| 969 | # remember the metadata of this specification |
||
| 970 | specification_metadata[uid] = metadata |
||
| 971 | # get the spec'd service UIDs |
||
| 972 | service_uids = metadata["service_uids"] |
||
| 973 | # remember a mapping of specification uid -> spec'd services |
||
| 974 | specification_to_services[uid] = service_uids |
||
| 975 | # remember a mapping of service uid -> specifications |
||
| 976 | for service_uid in service_uids: |
||
| 977 | if service_uid in service_to_specifications: |
||
| 978 | service_to_specifications[service_uid].append(uid) |
||
| 979 | else: |
||
| 980 | service_to_specifications[service_uid] = [uid] |
||
| 981 | |||
| 982 | # AR TEMPLATES |
||
| 983 | for uid, obj in _templates.iteritems(): |
||
| 984 | # get the template metadata |
||
| 985 | metadata = self.get_template_info(obj) |
||
| 986 | # remember the template metadata |
||
| 987 | template_metadata[uid] = metadata |
||
| 988 | |||
| 989 | # XXX notify below to include the drymatter service as well |
||
| 990 | record["ReportDryMatter"] = obj.getReportDryMatter() |
||
| 991 | |||
| 992 | # profile from the template |
||
| 993 | profile = obj.getAnalysisProfile() |
||
| 994 | # add the profile to the other profiles |
||
| 995 | if profile is not None: |
||
| 996 | profile_uid = api.get_uid(profile) |
||
| 997 | _profiles[profile_uid] = profile |
||
| 998 | |||
| 999 | # get the template analyses |
||
| 1000 | # [{'partition': 'part-1', 'service_uid': 'a6c5ff56a00e427a884e313d7344f966'}, |
||
| 1001 | # {'partition': 'part-1', 'service_uid': 'dd6b0f756a5b4b17b86f72188ee81c80'}] |
||
| 1002 | analyses = obj.getAnalyses() or [] |
||
| 1003 | # get all UIDs of the template records |
||
| 1004 | service_uids = map(lambda rec: rec.get("service_uid"), analyses) |
||
| 1005 | # remember a mapping of template uid -> service |
||
| 1006 | template_to_services[uid] = service_uids |
||
| 1007 | # remember a mapping of service uid -> templates |
||
| 1008 | for service_uid in service_uids: |
||
| 1009 | # append service to services mapping |
||
| 1010 | service = self.get_object_by_uid(service_uid) |
||
| 1011 | # remember the template of all services |
||
| 1012 | if service_uid in service_to_templates: |
||
| 1013 | service_to_templates[service_uid].append(uid) |
||
| 1014 | else: |
||
| 1015 | service_to_templates[service_uid] = [uid] |
||
| 1016 | |||
| 1017 | # DRY MATTER |
||
| 1018 | dms = self.get_drymatter_service() |
||
| 1019 | if dms and record.get("ReportDryMatter"): |
||
| 1020 | # get the UID of the drymatter service |
||
| 1021 | dms_uid = api.get_uid(dms) |
||
| 1022 | # get the drymatter metadata |
||
| 1023 | metadata = self.get_service_info(dms) |
||
| 1024 | # remember the metadata of the drymatter service |
||
| 1025 | dms_metadata[dms_uid] = metadata |
||
| 1026 | # add the drymatter service to the service collection (processed later) |
||
| 1027 | _services[dms_uid] = dms |
||
| 1028 | # get the dependencies of the drymatter service |
||
| 1029 | dms_deps = self.get_calculation_dependencies_for(dms) |
||
| 1030 | # add the drymatter service dependencies to the service collection (processed later) |
||
| 1031 | _services.update(dms_deps) |
||
| 1032 | # remember a mapping of dms uid -> services |
||
| 1033 | dms_to_services[dms_uid] = dms_deps.keys() + [dms_uid] |
||
| 1034 | # remember a mapping of dms dependency uid -> dms |
||
| 1035 | service_to_dms[dms_uid] = [dms_uid] |
||
| 1036 | for dep_uid, dep in dms_deps.iteritems(): |
||
| 1037 | if dep_uid in service_to_dms: |
||
| 1038 | service_to_dms[dep_uid].append(dms_uid) |
||
| 1039 | else: |
||
| 1040 | service_to_dms[dep_uid] = [dms_uid] |
||
| 1041 | |||
| 1042 | # PROFILES |
||
| 1043 | for uid, obj in _profiles.iteritems(): |
||
| 1044 | # get the profile metadata |
||
| 1045 | metadata = self.get_profile_info(obj) |
||
| 1046 | # remember the profile metadata |
||
| 1047 | profile_metadata[uid] = metadata |
||
| 1048 | # get all services of this profile |
||
| 1049 | services = obj.getService() |
||
| 1050 | # get all UIDs of the profile services |
||
| 1051 | service_uids = map(api.get_uid, services) |
||
| 1052 | # remember all services of this profile |
||
| 1053 | profile_to_services[uid] = service_uids |
||
| 1054 | # remember a mapping of service uid -> profiles |
||
| 1055 | for service in services: |
||
| 1056 | # get the UID of this service |
||
| 1057 | service_uid = api.get_uid(service) |
||
| 1058 | # add the service to the other services |
||
| 1059 | _services[service_uid] = service |
||
| 1060 | # remember the profiles of this service |
||
| 1061 | if service_uid in service_to_profiles: |
||
| 1062 | service_to_profiles[service_uid].append(uid) |
||
| 1063 | else: |
||
| 1064 | service_to_profiles[service_uid] = [uid] |
||
| 1065 | |||
| 1066 | # SAMPLES |
||
| 1067 | for uid, obj in _samples.iteritems(): |
||
| 1068 | # get the sample metadata |
||
| 1069 | metadata = self.get_sample_info(obj) |
||
| 1070 | # remember the sample metadata |
||
| 1071 | sample_metadata[uid] = metadata |
||
| 1072 | |||
| 1073 | # SAMPLETYPES |
||
| 1074 | for uid, obj in _sampletypes.iteritems(): |
||
| 1075 | # get the sampletype metadata |
||
| 1076 | metadata = self.get_sampletype_info(obj) |
||
| 1077 | # remember the sampletype metadata |
||
| 1078 | sampletype_metadata[uid] = metadata |
||
| 1079 | |||
| 1080 | # SERVICES |
||
| 1081 | for uid, obj in _services.iteritems(): |
||
| 1082 | # get the service metadata |
||
| 1083 | metadata = self.get_service_info(obj) |
||
| 1084 | |||
| 1085 | # N.B.: Partitions only handled via AR Template. |
||
| 1086 | # |
||
| 1087 | # # Partition setup for the give sample type |
||
| 1088 | # for st_uid, st_obj in _sampletypes.iteritems(): |
||
| 1089 | # # remember the partition setup for this service |
||
| 1090 | # metadata["partitions"] = self.get_service_partitions(obj, st_obj) |
||
| 1091 | |||
| 1092 | # remember the services' metadata |
||
| 1093 | service_metadata[uid] = metadata |
||
| 1094 | |||
| 1095 | # DEPENDENCIES |
||
| 1096 | for uid, obj in _services.iteritems(): |
||
| 1097 | # get the dependencies of this service |
||
| 1098 | deps = self.get_service_dependencies_for(obj) |
||
| 1099 | |||
| 1100 | # check for unmet dependencies |
||
| 1101 | for dep in deps["dependencies"]: |
||
| 1102 | # we use the UID to test for equality |
||
| 1103 | dep_uid = api.get_uid(dep) |
||
| 1104 | if dep_uid not in _services.keys(): |
||
| 1105 | if uid in unmet_dependencies: |
||
| 1106 | unmet_dependencies[uid].append(self.get_base_info(dep)) |
||
| 1107 | else: |
||
| 1108 | unmet_dependencies[uid] = [self.get_base_info(dep)] |
||
| 1109 | # remember the dependencies in the service metadata |
||
| 1110 | service_metadata[uid].update({ |
||
| 1111 | "dependencies": map(self.get_base_info, deps["dependencies"]), |
||
| 1112 | "dependants": map(self.get_base_info, deps["dependants"]), |
||
| 1113 | }) |
||
| 1114 | |||
| 1115 | # Each key `n` (1,2,3...) contains the form data for one AR Add |
||
| 1116 | # column in the UI. |
||
| 1117 | # All relevant form data will be set accoriding to this data. |
||
| 1118 | out[n] = { |
||
| 1119 | "client_metadata": client_metadata, |
||
| 1120 | "contact_metadata": contact_metadata, |
||
| 1121 | "sample_metadata": sample_metadata, |
||
| 1122 | "sampletype_metadata": sampletype_metadata, |
||
| 1123 | "dms_metadata": dms_metadata, |
||
| 1124 | "dms_to_services": dms_to_services, |
||
| 1125 | "service_to_dms": service_to_dms, |
||
| 1126 | "specification_metadata": specification_metadata, |
||
| 1127 | "specification_to_services": specification_to_services, |
||
| 1128 | "service_to_specifications": service_to_specifications, |
||
| 1129 | "template_metadata": template_metadata, |
||
| 1130 | "template_to_services": template_to_services, |
||
| 1131 | "service_to_templates": service_to_templates, |
||
| 1132 | "profile_metadata": profile_metadata, |
||
| 1133 | "profile_to_services": profile_to_services, |
||
| 1134 | "service_to_profiles": service_to_profiles, |
||
| 1135 | "service_metadata": service_metadata, |
||
| 1136 | "unmet_dependencies": unmet_dependencies, |
||
| 1137 | } |
||
| 1138 | |||
| 1139 | return out |
||
| 1140 | |||
| 1141 | def show_recalculate_prices(self): |
||
| 1142 | bika_setup = api.get_bika_setup() |
||
| 1143 | return bika_setup.getShowPrices() |
||
| 1144 | |||
| 1145 | def ajax_recalculate_prices(self): |
||
| 1146 | """Recalculate prices for all ARs |
||
| 1147 | """ |
||
| 1148 | # When the option "Include and display pricing information" in |
||
| 1149 | # Bika Setup Accounting tab is not selected |
||
| 1150 | if not self.show_recalculate_prices(): |
||
| 1151 | return {} |
||
| 1152 | |||
| 1153 | # The sorted records from the request |
||
| 1154 | records = self.get_records() |
||
| 1155 | |||
| 1156 | client = self.get_client() |
||
| 1157 | bika_setup = api.get_bika_setup() |
||
| 1158 | |||
| 1159 | member_discount = float(bika_setup.getMemberDiscount()) |
||
| 1160 | member_discount_applies = False |
||
| 1161 | if client: |
||
| 1162 | member_discount_applies = client.getMemberDiscountApplies() |
||
| 1163 | |||
| 1164 | prices = {} |
||
| 1165 | for n, record in enumerate(records): |
||
| 1166 | ardiscount_amount = 0.00 |
||
| 1167 | arservices_price = 0.00 |
||
| 1168 | arprofiles_price = 0.00 |
||
| 1169 | arprofiles_vat_amount = 0.00 |
||
| 1170 | arservice_vat_amount = 0.00 |
||
| 1171 | services_from_priced_profile = [] |
||
| 1172 | |||
| 1173 | profile_uids = record.get("Profiles_uid", "").split(",") |
||
| 1174 | profile_uids = filter(lambda x: x, profile_uids) |
||
| 1175 | profiles = map(self.get_object_by_uid, profile_uids) |
||
| 1176 | services = map(self.get_object_by_uid, record.get("Analyses", [])) |
||
| 1177 | |||
| 1178 | # ANALYSIS PROFILES PRICE |
||
| 1179 | for profile in profiles: |
||
| 1180 | use_profile_price = profile.getUseAnalysisProfilePrice() |
||
| 1181 | if not use_profile_price: |
||
| 1182 | continue |
||
| 1183 | |||
| 1184 | profile_price = float(profile.getAnalysisProfilePrice()) |
||
| 1185 | profile_vat = float(profile.getAnalysisProfileVAT()) |
||
| 1186 | arprofiles_price += profile_price |
||
| 1187 | arprofiles_vat_amount += profile_vat |
||
| 1188 | profile_services = profile.getService() |
||
| 1189 | services_from_priced_profile.extend(profile_services) |
||
| 1190 | |||
| 1191 | # ANALYSIS SERVICES PRICE |
||
| 1192 | for service in services: |
||
| 1193 | if service in services_from_priced_profile: |
||
| 1194 | continue |
||
| 1195 | service_price = float(service.getPrice()) |
||
| 1196 | # service_vat = float(service.getVAT()) |
||
| 1197 | service_vat_amount = float(service.getVATAmount()) |
||
| 1198 | arservice_vat_amount += service_vat_amount |
||
| 1199 | arservices_price += service_price |
||
| 1200 | |||
| 1201 | base_price = arservices_price + arprofiles_price |
||
| 1202 | |||
| 1203 | # Calculate the member discount if it applies |
||
| 1204 | if member_discount and member_discount_applies: |
||
| 1205 | logger.info("Member discount applies with {}%".format(member_discount)) |
||
| 1206 | ardiscount_amount = base_price * member_discount / 100 |
||
| 1207 | |||
| 1208 | subtotal = base_price - ardiscount_amount |
||
| 1209 | vat_amount = arprofiles_vat_amount + arservice_vat_amount |
||
| 1210 | total = subtotal + vat_amount |
||
| 1211 | |||
| 1212 | prices[n] = { |
||
| 1213 | "discount": "{0:.2f}".format(ardiscount_amount), |
||
| 1214 | "subtotal": "{0:.2f}".format(subtotal), |
||
| 1215 | "vat": "{0:.2f}".format(vat_amount), |
||
| 1216 | "total": "{0:.2f}".format(total), |
||
| 1217 | } |
||
| 1218 | logger.info("Prices for AR {}: Discount={discount} " |
||
| 1219 | "VAT={vat} Subtotal={subtotal} total={total}" |
||
| 1220 | .format(n, **prices[n])) |
||
| 1221 | |||
| 1222 | return prices |
||
| 1223 | |||
| 1224 | def ajax_submit(self): |
||
| 1225 | """Submit & create the ARs |
||
| 1226 | """ |
||
| 1227 | |||
| 1228 | # Get AR required fields (including extended fields) |
||
| 1229 | fields = self.get_obj_fields() |
||
| 1230 | |||
| 1231 | # extract records from request |
||
| 1232 | records = self.get_records() |
||
| 1233 | |||
| 1234 | fielderrors = {} |
||
| 1235 | errors = {"message": "", "fielderrors": {}} |
||
| 1236 | |||
| 1237 | attachments = {} |
||
| 1238 | valid_records = [] |
||
| 1239 | |||
| 1240 | # Validate required fields |
||
| 1241 | for n, record in enumerate(records): |
||
| 1242 | |||
| 1243 | # Process UID fields first and set their values to the linked field |
||
| 1244 | uid_fields = filter(lambda f: f.endswith("_uid"), record) |
||
| 1245 | for field in uid_fields: |
||
| 1246 | name = field.replace("_uid", "") |
||
| 1247 | value = record.get(field) |
||
| 1248 | if "," in value: |
||
| 1249 | value = value.split(",") |
||
| 1250 | record[name] = value |
||
| 1251 | |||
| 1252 | # Extract file uploads (fields ending with _file) |
||
| 1253 | # These files will be added later as attachments |
||
| 1254 | file_fields = filter(lambda f: f.endswith("_file"), record) |
||
| 1255 | attachments[n] = map(lambda f: record.pop(f), file_fields) |
||
| 1256 | |||
| 1257 | # Process Specifications field (dictionary like records instance). |
||
| 1258 | # -> Convert to a standard Python dictionary. |
||
| 1259 | specifications = map(lambda x: dict(x), record.pop("Specifications", [])) |
||
| 1260 | record["Specifications"] = specifications |
||
| 1261 | |||
| 1262 | # Required fields and their values |
||
| 1263 | required_keys = [field.getName() for field in fields if field.required] |
||
| 1264 | required_values = [record.get(key) for key in required_keys] |
||
| 1265 | required_fields = dict(zip(required_keys, required_values)) |
||
| 1266 | |||
| 1267 | # Client field is required but hidden in the AR Add form. We remove |
||
| 1268 | # it therefore from the list of required fields to let empty |
||
| 1269 | # columns pass the required check below. |
||
| 1270 | if record.get("Client", False): |
||
| 1271 | required_fields.pop('Client', None) |
||
| 1272 | |||
| 1273 | # Contacts get pre-filled out if only one contact exists. |
||
| 1274 | # We won't force those columns with only the Contact filled out to be required. |
||
| 1275 | contact = required_fields.pop("Contact", None) |
||
| 1276 | |||
| 1277 | # None of the required fields are filled, skip this record |
||
| 1278 | if not any(required_fields.values()): |
||
| 1279 | continue |
||
| 1280 | |||
| 1281 | # Re-add the Contact |
||
| 1282 | required_fields["Contact"] = contact |
||
| 1283 | |||
| 1284 | # Missing required fields |
||
| 1285 | missing = [f for f in required_fields if not record.get(f, None)] |
||
| 1286 | |||
| 1287 | # If there are required fields missing, flag an error |
||
| 1288 | for field in missing: |
||
| 1289 | fieldname = "{}-{}".format(field, n) |
||
| 1290 | msg = _("Field '{}' is required".format(field)) |
||
| 1291 | fielderrors[fieldname] = msg |
||
| 1292 | |||
| 1293 | # Selected Analysis UIDs |
||
| 1294 | selected_analysis_uids = record.get("Analyses", []) |
||
| 1295 | |||
| 1296 | # Partitions defined in Template |
||
| 1297 | template_parts = {} |
||
| 1298 | template_uid = record.get("Template_uid") |
||
| 1299 | if template_uid: |
||
| 1300 | template = api.get_object_by_uid(template_uid) |
||
| 1301 | for part in template.getPartitions(): |
||
| 1302 | # remember the part setup by part_id |
||
| 1303 | template_parts[part.get("part_id")] = part |
||
| 1304 | |||
| 1305 | # The final data structure should look like this: |
||
| 1306 | # [{"part_id": "...", "container_uid": "...", "services": []}] |
||
| 1307 | partitions = {} |
||
| 1308 | parts = record.pop("Parts", []) |
||
| 1309 | for part in parts: |
||
| 1310 | part_id = part.get("part") |
||
| 1311 | service_uid = part.get("uid") |
||
| 1312 | # skip unselected Services |
||
| 1313 | if service_uid not in selected_analysis_uids: |
||
| 1314 | continue |
||
| 1315 | # Container UID for this part |
||
| 1316 | container_uids = [] |
||
| 1317 | template_part = template_parts.get(part_id) |
||
| 1318 | if template_part: |
||
| 1319 | container_uid = template_part.get("container_uid") |
||
| 1320 | if container_uid: |
||
| 1321 | container_uids.append(container_uid) |
||
| 1322 | |||
| 1323 | # remember the part id and the services |
||
| 1324 | if part_id not in partitions: |
||
| 1325 | partitions[part_id] = { |
||
| 1326 | "part_id": part_id, |
||
| 1327 | "container_uid": container_uids, |
||
| 1328 | "services": [service_uid], |
||
| 1329 | } |
||
| 1330 | else: |
||
| 1331 | partitions[part_id]["services"].append(service_uid) |
||
| 1332 | |||
| 1333 | # Inject the Partitions to the record (will be picked up during the AR creation) |
||
| 1334 | record["Partitions"] = partitions.values() |
||
| 1335 | |||
| 1336 | # Process valid record |
||
| 1337 | valid_record = dict() |
||
| 1338 | for fieldname, fieldvalue in record.iteritems(): |
||
| 1339 | # clean empty |
||
| 1340 | if fieldvalue in ['', None]: |
||
| 1341 | continue |
||
| 1342 | valid_record[fieldname] = fieldvalue |
||
| 1343 | |||
| 1344 | # append the valid record to the list of valid records |
||
| 1345 | valid_records.append(valid_record) |
||
| 1346 | |||
| 1347 | # return immediately with an error response if some field checks failed |
||
| 1348 | if fielderrors: |
||
| 1349 | errors["fielderrors"] = fielderrors |
||
| 1350 | return {'errors': errors} |
||
| 1351 | |||
| 1352 | # Process Form |
||
| 1353 | ARs = [] |
||
| 1354 | for n, record in enumerate(valid_records): |
||
| 1355 | client_uid = record.get("Client") |
||
| 1356 | client = self.get_object_by_uid(client_uid) |
||
| 1357 | |||
| 1358 | if not client: |
||
| 1359 | raise RuntimeError("No client found") |
||
| 1360 | |||
| 1361 | # get the specifications and pass them directly to the AR create function. |
||
| 1362 | specifications = record.pop("Specifications", {}) |
||
| 1363 | |||
| 1364 | # Create the Analysis Request |
||
| 1365 | try: |
||
| 1366 | ar = crar(client, self.request, record, specifications=specifications) |
||
| 1367 | except (KeyError, RuntimeError) as e: |
||
| 1368 | errors["message"] = e.message |
||
| 1369 | return {"errors": errors} |
||
| 1370 | ARs.append(ar.Title()) |
||
| 1371 | |||
| 1372 | _attachments = [] |
||
| 1373 | for attachment in attachments.get(n, []): |
||
| 1374 | if not attachment.filename: |
||
| 1375 | continue |
||
| 1376 | att = _createObjectByType("Attachment", self.context, tmpID()) |
||
| 1377 | att.setAttachmentFile(attachment) |
||
| 1378 | att.processForm() |
||
| 1379 | _attachments.append(att) |
||
| 1380 | if _attachments: |
||
| 1381 | ar.setAttachment(_attachments) |
||
| 1382 | |||
| 1383 | level = "info" |
||
| 1384 | if len(ARs) == 0: |
||
| 1385 | message = _('No Analysis Requests could be created.') |
||
| 1386 | level = "error" |
||
| 1387 | elif len(ARs) > 1: |
||
| 1388 | message = _('Analysis requests ${ARs} were successfully created.', |
||
| 1389 | mapping={'ARs': safe_unicode(', '.join(ARs))}) |
||
| 1390 | else: |
||
| 1391 | message = _('Analysis request ${AR} was successfully created.', |
||
| 1392 | mapping={'AR': safe_unicode(ARs[0])}) |
||
| 1393 | |||
| 1394 | # Display a portal message |
||
| 1395 | self.context.plone_utils.addPortalMessage(message, level) |
||
| 1396 | |||
| 1397 | # Automatic label printing won't print "register" labels for Secondary. ARs |
||
| 1398 | bika_setup = api.get_bika_setup() |
||
| 1399 | auto_print = bika_setup.getAutoPrintStickers() |
||
| 1400 | |||
| 1401 | # https://github.com/bikalabs/bika.lims/pull/2153 |
||
| 1402 | new_ars = [a for a in ARs if a[-1] == '1'] |
||
| 1403 | |||
| 1404 | if 'register' in auto_print and new_ars: |
||
| 1405 | return { |
||
| 1406 | 'success': message, |
||
| 1407 | 'stickers': new_ars, |
||
| 1408 | 'stickertemplate': self.context.bika_setup.getAutoStickerTemplate() |
||
| 1409 | } |
||
| 1410 | else: |
||
| 1411 | return {'success': message} |
||
| 1412 |