+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::Part - Part CRUD controller
+
+=head1 DESCRIPTION
+
+Controller for adding/editing/saving/deleting parts.
+
+All the relations are loaded at once and saving the part, adding a history
+entry and saving CVars happens inside one transaction. When saving the old
+relations are deleted and written as new to the database.
+
+Relations for parts:
+
+=over 2
+
+=item makemodels
+
+=item translations
+
+=item assembly items
+
+=item assortment items
+
+=item prices
+
+=back
+
+=head1 PART_TYPES
+
+There are 4 different part types:
+
+=over 4
+
+=item C<part>
+
+The "default" part type.
+
+inventory_accno_id is set.
+
+=item C<service>
+
+Services can't be stocked.
+
+inventory_accno_id isn't set.
+
+=item C<assembly>
+
+Assemblies consist of other parts, services, assemblies or assortments. They
+aren't meant to be bought, only sold. To add assemblies to stock you typically
+have to make them, which reduces the stock by its respective components. Once
+an assembly item has been created there is currently no way to "disassemble" it
+again. An assembly item can appear several times in one assembly. An assmbly is
+sold as one item with a defined sellprice and lastcost. If the component prices
+change the assortment price remains the same. The assembly items may be printed
+in a record if the item's "bom" is set.
+
+=item C<assortment>
+
+Similar to assembly, but each assortment item may only appear once per
+assortment. When selling an assortment the assortment items are added to the
+record together with the assortment, which is added with sellprice 0.
+
+Technically an assortment doesn't have a sellprice, but rather the sellprice is
+determined by the sum of the current assortment item prices when the assortment
+is added to a record. This also means that price rules and customer discounts
+will be applied to the assortment items.
+
+Once the assortment items have been added they may be modified or deleted, just
+as if they had been added manually, the individual assortment items aren't
+linked to the assortment or the other assortment items in any way.
+
+=back
+
+=head1 URL ACTIONS
+
+=over 4
+
+=item C<action_add_part>
+
+=item C<action_add_service>
+
+=item C<action_add_assembly>
+
+=item C<action_add_assortment>
+
+=item C<action_add PART_TYPE>
+
+An alternative to the action_add_$PART_TYPE actions, takes the mandatory
+parameter part_type as an action. Example:
+
+ controller.pl?action=Part/add&part_type=service
+
+=item C<action_save>
+
+Saves the current part and then reloads the edit page for the part.
+
+=item C<action_use_as_new>
+
+Takes the information from the current part, plus any modifications made on the
+page, and creates a new edit page that is ready to be saved. The partnumber is
+set empty, so a new partnumber from the number range will be used if the user
+doesn't enter one manually.
+
+Unsaved changes to the original part aren't updated.
+
+The part type cannot be changed in this way.
+
+=item C<action_delete>
+
+Deletes the current part and then redirects to the main page, there is no
+callback.
+
+The delete button only appears if the part is 'orphaned', according to
+SL::DB::Part orphaned.
+
+The part can't be deleted if it appears in invoices, orders, delivery orders,
+the inventory, or is part of an assembly or assortment.
+
+If the part is deleted its relations prices, makdemodel, assembly,
+assortment_items and translation are are also deleted via DELETE ON CASCADE.
+
+Before this controller items that appeared in inventory didn't count as
+orphaned and could be deleted and the inventory entries were also deleted, this
+"feature" hasn't been implemented.
+
+=item C<action_edit part.id>
+
+Load and display a part for editing.
+
+ controller.pl?action=Part/edit&part.id=12345
+
+Passing the part id is mandatory, and the parameter is "part.id", not "id".
+
+=back
+
+=head1 BUTTON ACTIONS
+
+=over 4
+
+=item C<history>
+
+Opens a popup displaying all the history entries. Once a new history controller
+is written the button could link there instead, with the part already selected.
+
+=back
+
+=head1 AJAX ACTIONS
+
+=over 4
+
+=item C<action_update_item_totals>
+
+Is called whenever an element with the .recalc class loses focus, e.g. the qty
+amount of an item changes. The sum of all sellprices and lastcosts is
+calculated and the totals updated. Uses C<recalc_item_totals>.
+
+=item C<action_add_assortment_item>
+
+Adds a new assortment item from a part picker seleciton to the assortment item list
+
+If the item already exists in the assortment the item isn't added and a Flash
+error shown.
+
+Rather than running kivi.Part.renumber_positions and kivi.Part.assembly_recalc
+after adding each new item, add the new object to the item objects that were
+already parsed, calculate totals via a dummy part then update the row and the
+totals.
+
+=item C<action_add_assembly_item>
+
+Adds a new assembly item from a part picker seleciton to the assembly item list
+
+If the item already exists in the assembly a flash info is generated, but the
+item is added.
+
+Rather than running kivi.Part.renumber_positions and kivi.Part.assembly_recalc
+after adding each new item, add the new object to the item objects that were
+already parsed, calculate totals via a dummy part then update the row and the
+totals.
+
+=item C<action_add_multi_assortment_items>
+
+Parses the items to be added from the form generated by the multi input and
+appends the html of the tr-rows to the assortment item table. Afterwards all
+assortment items are renumbered and the sums recalculated via
+kivi.Part.renumber_positions and kivi.Part.assortment_recalc.
+
+=item C<action_add_multi_assembly_items>
+
+Parses the items to be added from the form generated by the multi input and
+appends the html of the tr-rows to the assembly item table. Afterwards all
+assembly items are renumbered and the sums recalculated via
+kivi.Part.renumber_positions and kivi.Part.assembly_recalc.
+
+=item C<action_show_multi_items_dialog>
+
+=item C<action_multi_items_update_result>
+
+=item C<action_add_makemodel_row>
+
+Add a new makemodel row with the vendor that was selected via the vendor
+picker.
+
+Checks the already existing makemodels and warns if a row with that vendor
+already exists. Currently it is possible to have duplicate vendor rows.
+
+=item C<action_reorder_items>
+
+Sorts the item table for assembly or assortment items.
+
+=item C<action_warehouse_changed>
+
+=back
+
+=head1 ACTIONS part picker
+
+=over 4
+
+=item C<action_ajax_autocomplete>
+
+=item C<action_test_page>
+
+=item C<action_part_picker_search>
+
+=item C<action_part_picker_result>
+
+=item C<action_show>
+
+=back
+
+=head1 FORM CHECKS
+
+=over 2
+
+=item C<check_form>
+
+Calls some simple checks that test the submitted $::form for obvious errors.
+Return 1 if all the tests were successfull, 0 as soon as one test fails.
+
+Errors from the failed tests are stored as ClientJS actions in $self->js. In
+some cases extra actions are taken, e.g. if the part description is missing the
+basic data tab is selected and the description input field is focussed.
+
+=back
+
+=over 4
+
+=item C<form_check_part_description_exists>
+
+=item C<form_check_assortment_items_exist>
+
+=item C<form_check_assortment_items_unique>
+
+=item C<form_check_assembly_items_exist>
+
+=item C<form_check_partnumber_is_unique>
+
+=back
+
+=head1 HELPER FUNCTIONS
+
+=over 4
+
+=item C<parse_form>
+
+When submitting the form for saving, parses the transmitted form. Expects the
+following data:
+
+ $::form->{part}
+ $::form->{makemodels}
+ $::form->{translations}
+ $::form->{prices}
+ $::form->{assemblies}
+ $::form->{assortments}
+
+CVar data is currently stored directly in $::form, e.g. $::form->{cvar_size}.
+
+=item C<recalc_item_totals %params>
+
+Helper function for calculating the total lastcost and sellprice for assemblies
+or assortments according to their items, which are parsed from the current
+$::form.
+
+Is called whenever the qty of an item is changed or items are deleted.
+
+Takes two params:
+
+* part_type : 'assortment' or 'assembly' (mandatory)
+
+* price_type: 'lastcost' or 'sellprice', default is 'sellprice'
+
+Depending on the price_type the lastcost sum or sellprice sum is returned.
+
+Doesn't work for recursive items.
+
+=back
+
+=head1 GET SET INITS
+
+There are get_set_inits for
+
+* assembly items
+
+* assortment items
+
+* makemodels
+
+which parse $::form and automatically create an array of objects.
+
+These inits are used during saving and each time a new element is added.
+
+=over 4
+
+=item C<init_makemodels>
+
+Parses $::form->{makemodels}, creates an array of makemodel objects and stores them in
+$self->part->makemodels, ready to be saved.
+
+Used for saving parts and adding new makemodel rows.
+
+=item C<parse_add_items_to_objects PART_TYPE>
+
+Parses the resulting form from either the part-picker submit or the multi-item
+submit, and creates an arrayref of assortment_item or assembly objects, that
+can be rendered via C<render_assortment_items_to_html> or
+C<render_assembly_items_to_html>.
+
+Mandatory param: part_type: assortment or assembly (the resulting html will differ)
+Optional param: position (used for numbering and listrow class)
+
+=item C<render_assortment_items_to_html ITEM_OBJECTS>
+
+Takes an array_ref of assortment_items, and generates tables rows ready for
+adding to the assortment table. Is used when a part is loaded, or whenever new
+assortment items are added.
+
+=item C<parse_form_makemodels>
+
+Makemodels can't just be overwritten, because of the field "lastupdate", that
+remembers when the lastcost for that vendor changed the last time.
+
+So the original values are cloned and remembered, so we can compare if lastcost
+was changed in $::form, and keep or update lastupdate.
+
+lastcost isn't updated until the first time it was saved with a value, until
+then it is empty.
+
+Also a boolean "makemodel" needs to be written in parts, depending on whether
+makemodel entries exist or not.
+
+We still need init_makemodels for when we open the part for editing.
+
+=back
+
+=head1 TODO
+
+=over 4
+
+=item *
+
+It should be possible to jump to the edit page in a specific tab
+
+=item *
+
+Support callbacks, e.g. creating a new part from within an order, and jumping
+back to the order again afterwards.
+
+=item *
+
+Support units when adding assembly items or assortment items. Currently the
+default unit of the item is always used.
+
+=item *
+
+Calculate sellprice and lastcost totals recursively, in case e.g. an assembly
+consists of other assemblies.
+
+=back
+
+=head1 AUTHOR
+
+G. Richardson E<lt>grichardson@kivitendo-premium.deE<gt>
+
+=cut